From 3d8de06494149d0c993f92dcce4fe0ce253b0502 Mon Sep 17 00:00:00 2001 From: Sanj Date: Thu, 14 Jun 2012 20:41:39 +0530 Subject: [PATCH] added many comments, much code cleanup --- itf/app/models.py | 390 +++++++++++++++++++++++---------------- itf/insidepages/urls.py | 6 +- itf/insidepages/views.py | 185 +++++++++++-------- 3 files changed, 334 insertions(+), 247 deletions(-) diff --git a/itf/app/models.py b/itf/app/models.py index e40cc8f..65e3a1e 100755 --- a/itf/app/models.py +++ b/itf/app/models.py @@ -29,17 +29,24 @@ def get_real_ctype(module_name): pass return None -class ItfModel(models.Model): - fts_fields = [] - fk_filters = [] -# related_models = [] - sort_fields = [] - hasComments = True - title_field = "title" - is_itf_model = True - changed = models.DateTimeField(null=True, editable=False) - created = models.DateTimeField(null=True, editable=False) +''' +Base models class that all other models that have front-end views inherit from. It handles passing of parameters to generate the list of items on the left, as well as methods (that can be over-ridden) to generate the context to be sent to the template that renders the object. +''' +class ItfModel(models.Model): + fts_fields = [] #Fields that should be text-searchable through + fk_filters = [] #Foreign key fields that should be filterable by +# related_models = [] + sort_fields = [] #Fields that the model can be sorted by + hasComments = True #Whether comments should be enabled for this model + form_names = [] #Names of forms, defined in /forms.py for this model + title_field = "title" #The db field that corresponds to the 'title' to display + is_itf_model = True #FIXME: remove this property, one should use isinstance(foo, ItfModel) instead to check if something is_itf_model + changed = models.DateTimeField(null=True, editable=False) #All models should have a changed timestamp + created = models.DateTimeField(null=True, editable=False) #All models should have a created timestamp + + + #For all models, on save, update their "changed" timestamp - if created, set their created timestamp. def save(self, *args, **kwargs): ''' On save, update timestamps ''' if not self.id: @@ -52,35 +59,36 @@ class ItfModel(models.Model): class Meta: abstract = True + #Should return the id for the object and the title to display in the left hand list def list_dict(self): - return {} + return { + 'id': self.pk, + 'title': self.get_title() + } + #This should return all the context for the object that will get passed to the sub-template for this object. def info_dict(self): d = self.get_dict() - d['add_form'] = self.__class__.get_add_form() +# d['add_form'] = self.__class__.get_add_form() + d['forms'] = self.__class__.get_forms() return d + + #Returns the title for this object, used as title of page, title of object, and by default, title in left hand display. By default, is a field called 'title', but subclasses can over-ride either title field or the get_title method and return anything. def get_title(self): return self.get(self.title_field) + + #Get the ModuleTab object associated with this object. def get_tab(self): modelextra = self.get_modelextra() tab = ModuleTab.objects.filter(model=modelextra)[0] return tab - #TODO: Get rid off and use .get_forms() instead - @classmethod - def get_add_form(cls): - try: - formStr = cls.add_form -# cls = default_tab.model_class() - app_label = cls._meta.app_label - module = __import__(app_label + ".forms") - add_form = module.forms.__getattribute__(formStr) - return add_form - except: - return None - + + ''' + Get all the forms associated with this class + ''' @classmethod def get_forms(cls): @@ -95,7 +103,9 @@ class ItfModel(models.Model): return ret - + ''' + Get the insidepages.models.ModelExtra object associated with this model + ''' def get_modelextra(self): try: ctype = ContentType.objects.get(model=self.__class__._meta.module_name) @@ -105,9 +115,16 @@ class ItfModel(models.Model): modelextra = ctype.modelextra_set.all()[0] return modelextra + ''' + Get absolute URL for this instance + ''' def get_absolute_url(self): return "%s/?tab=%s&object_id=%d" % (self.get_module().get_absolute_url(), self.get_tab().slug, self.id) + + ''' + Get insidepages.models.Module instance for this object + ''' def get_module(self): tab = self.get_tab() if tab: @@ -117,6 +134,9 @@ class ItfModel(models.Model): + ''' + Get the main image thumbnail for this object - checks if object has either a field or custom method called 'main_image', then checks for default_images on the ModelExtra for this model. + ''' def get_main_image(self, size="142x150"): if hasattr(self, 'main_image'): main_image_getter = self.main_image @@ -146,6 +166,9 @@ class ItfModel(models.Model): return None ''' + ''' + The templates for objects are stored following the convention templates/modules//.html + ''' def get_template_path(self): kls = self.__class__ return "modules/%s/%s.html" % (kls._meta.app_label, kls._meta.module_name) @@ -153,6 +176,10 @@ class ItfModel(models.Model): def get_dict(self): return self.get(self._get_fields().keys()) + + ''' + Renders the html for this object by taking .info_dict() and rendering it to .get_template_path() . Subclasses should not over-ride this - over-ride .get_dict() ideally to pass custom data. + ''' def insidepage_dict(self, request): context = RequestContext(request, self.info_dict()) html = render_to_string(self.get_template_path(), context) @@ -165,6 +192,156 @@ class ItfModel(models.Model): + + ''' + Filters a queryset to return only objects whose fts_fields contain search term. FIXME: Ideally, this would be handled by the search back-end and not __icontains queries in the db. + ''' + @classmethod + def fts(kls, qset, search): + terms = splitSearch(search) + qobjects = [] + for t in terms: + for f in kls.fts_fields: + qstring = f + '__icontains' + qobject = Q(**{qstring:t}) + qobjects.append(qobject) + return qset.filter(reduce(operator.or_, qobjects)) + + + ''' + Main function to return list of items for this model class - optionally filtered by various parameters + ''' + @classmethod + def get_list(kls, data): + options = { + 'page': 1, + 'count': 20, + 'search': '', + 'sort': [], + 'range': [0,50], + 'object_id': False + } + options.update(data) + ret = [] + page_no = options['page'] + list_size = options['count'] + + #Get the full queryset - defaults to kls.objects.all() + qset = kls.get_qset() + + #If search term exists, filter by it. + search = options['search'] + if search != '': + qset = kls.fts(qset, search) + + + sort = options['sort'] + + #If sort is not an empty array, sort by sort options + if sort != []: + for s in sort: + if s['operator'] == '-': + operator = '-' + else: + operator = '' + sort = operator + s['key'] + qset = qset.order_by(sort) + + + #FIXME: This is a very ugly hack where to make sure the object that is currently selected displays on the current page, we make it the first item and remove it from the qset. + if options['object_id'] != False and options['object_id'] != '': + object_id = options['object_id'] + qset = qset.exclude(pk=object_id) + try: + obj = kls.objects.get(pk=object_id) + ret.append(obj.list_dict()) + except: + pass + + #Another slight hack - if there are 0 results returned, we set hasResults to False and return the entire queryset, telling the template to display a "No Results Found" message and let the user see all the items in the list. + hasResults = True + if qset.count() == 0: + hasResults = False + qset = kls.get_qset() + + ''' + r0 = options['range'][0] + r1 = options['range'][1] + results = qset[r0:r1] + ''' + + #Handle pagination through django Paginator class + paginator = Paginator(qset, list_size) + + + try: + results = paginator.page(page_no) + except (EmptyPage, InvalidPage): + results = paginator.page(paginator.num_pages) + + for r in results.object_list: + d = r.list_dict() + d['url'] = r.get_absolute_url() + ret.append(d) + + + return { + 'has_next': results.has_next(), + 'has_previous': results.has_previous(), + 'page_no': page_no, + 'has_results': hasResults, + 'num_pages': paginator.num_pages, + 'items': ret + } + + @classmethod + def get_qset(kls): + ''' + Override this method in your model class to define a custom queryset instead of objects.all(), for instance, to always exclude unpublished items. + ''' + return kls.objects.all() + + + + ''' + Function to filter a queryset by a foreignkey. NOT IMPLEMENTED. + eg. fks = { + 'somefield': [1, 5, 7], + 'someotherfield': [3] + } + ''' + @classmethod + def filter_by_fks(kls, qset, fks): + qobjects = [] + for key in fks.keys(): + field = getField(kls._meta.fields, key) + if field: +# rel_class = field.related.parent_model + for i in fks[key]: + qobject = Q(**{field.name: i}) + qobjects.append(qobject) + return qset.filter(reduce(operator.or_, qobjects)) + + + ''' + Some fairly meta functions to iterate through model fields, etc. - was written when trying to write a custom model.to_json() kindof function that 'just worked' for all field types - right now they're not used, but maybe useful in the future. + ''' + + @classmethod + def get_fk_objects(kls): + ret = {} + for f in kls.fk_filters: + ret[f] = [] + field = getField(kls._meta.fields, f) + rel_class = field.related.parent_model + for o in rel_class.objects.all(): + ret[f].append({ + 'id': o.id, + 'title': unicode(o) + }) + return ret + + def get(self, props): typ = type(props) if typ == list: @@ -228,133 +405,7 @@ class ItfModel(models.Model): return ret - - @classmethod - def fts(kls, qset, search): - terms = splitSearch(search) - qobjects = [] - for t in terms: - for f in kls.fts_fields: - qstring = f + '__icontains' - qobject = Q(**{qstring:t}) - qobjects.append(qobject) - return qset.filter(reduce(operator.or_, qobjects)) - ''' - eg. fks = { - 'somefield': [1, 5, 7], - 'someotherfield': [3] - } - ''' - @classmethod - def filter_by_fks(kls, qset, fks): - qobjects = [] - for key in fks.keys(): - field = getField(kls._meta.fields, key) - if field: -# rel_class = field.related.parent_model - for i in fks[key]: - qobject = Q(**{field.name: i}) - qobjects.append(qobject) - return qset.filter(reduce(operator.or_, qobjects)) - - - @classmethod - def get_fk_objects(kls): - ret = {} - for f in kls.fk_filters: - ret[f] = [] - field = getField(kls._meta.fields, f) - rel_class = field.related.parent_model - for o in rel_class.objects.all(): - ret[f].append({ - 'id': o.id, - 'title': unicode(o) - }) - return ret - - - @classmethod - def get_list(kls, data): - options = { - 'page': 1, - 'count': 20, - 'search': '', - 'sort': [], - 'range': [0,50], - 'object_id': False - } - options.update(data) - ret = [] - page_no = options['page'] - list_size = options['count'] - qset = kls.get_qset() - - search = options['search'] - if search != '': - qset = kls.fts(qset, search) - - sort = options['sort'] - - if sort != []: - for s in sort: - if s['operator'] == '-': - operator = '-' - else: - operator = '' - sort = operator + s['key'] - qset = qset.order_by(sort) - - - #FIXME: object_id needs to do something more graceful, this breaks sort. - if options['object_id'] != False and options['object_id'] != '': - object_id = options['object_id'] - qset = qset.exclude(pk=object_id) - try: - obj = kls.objects.get(pk=object_id) - ret.append(obj.list_dict()) - except: - pass - - hasResults = True - if qset.count() == 0: - hasResults = False - qset = kls.get_qset() - - ''' - r0 = options['range'][0] - r1 = options['range'][1] - results = qset[r0:r1] - ''' - paginator = Paginator(qset, list_size) - - - try: - results = paginator.page(page_no) - except (EmptyPage, InvalidPage): - results = paginator.page(paginator.num_pages) - - for r in results.object_list: - d = r.list_dict() - d['url'] = r.get_absolute_url() - ret.append(d) - - - return { - 'has_next': results.has_next(), - 'has_previous': results.has_previous(), - 'page_no': page_no, - 'has_results': hasResults, - 'num_pages': paginator.num_pages, - 'items': ret - } - - @classmethod - def get_qset(kls): - ''' - Override this method in your model class to define a custom queryset instead of objects.all(), for instance, to always exclude unpublished items. - ''' - return kls.objects.all() def getField(fields, name): for f in fields: @@ -364,13 +415,28 @@ def getField(fields, name): -def site_config(): - with open(settings.SITE_CONFIG) as f: - site_config = json.load(f) - site_config['keys'] = {} - for key in site_config['itemKeys']: - site_config['keys'][key['id']] = key - site_config['_findKeys'] = {} - for key in site_config['findKeys']: - site_config['_findKeys'][key['id']] = key - return site_config +#def site_config(): +# with open(settings.SITE_CONFIG) as f: +# site_config = json.load(f) +# site_config['keys'] = {} +# for key in site_config['itemKeys']: +# site_config['keys'][key['id']] = key +# site_config['_findKeys'] = {} +# for key in site_config['findKeys']: +# site_config['_findKeys'][key['id']] = key +# return site_config + + +# #TODO: Get rid off and use .get_forms() instead +# @classmethod +# def get_add_form(cls): +# try: +# formStr = cls.add_form +## cls = default_tab.model_class() +# app_label = cls._meta.app_label +# module = __import__(app_label + ".forms") +# add_form = module.forms.__getattribute__(formStr) +# return add_form +# except: +# return None + diff --git a/itf/insidepages/urls.py b/itf/insidepages/urls.py index 246ece9..1fd98c0 100755 --- a/itf/insidepages/urls.py +++ b/itf/insidepages/urls.py @@ -2,9 +2,9 @@ from django.conf.urls.defaults import * import views urlpatterns = patterns('', - (r'^get_list$', views.get_list), - (r'^get_details$', views.get_details), - (r'^get_tab$', views.get_tab), +# (r'^get_list$', views.get_list), +# (r'^get_details$', views.get_details), +# (r'^get_tab$', views.get_tab), #(r'^static/(?P.*)/$', views.render_object), (r'^(?P.*)/$', views.render_object), ) diff --git a/itf/insidepages/views.py b/itf/insidepages/views.py index 97e2663..7584d0a 100755 --- a/itf/insidepages/views.py +++ b/itf/insidepages/views.py @@ -4,84 +4,17 @@ from django.template import RequestContext from ox.django.shortcuts import render_to_json_response import re -def main(request, module_slug): - m = get_object_or_404(Module, slug=module_slug) - tabs = m.moduletab_set.all().order_by('order') - default_tab = tabs[0] - try: - formStr = default_tab.model_class().add_form - has_add = True - add_form_class = default_tab.model_class().get_add_form() - add_form = add_form_class() - except: - add_form = None - has_add = False - - list_options = {} #get some options as GET params, etc. to potentially pass to get_list - default_tab_list = default_tab.model_class().get_list(list_options) - context = RequestContext(request, { - 'title': m.title, - 'about': m.about, - 'has_add': has_add, - 'add_form': add_form, - 'default_tab': tabs[0], - 'default_list': default_tab_list, - 'default_sorts': default_tab.get_dict()['sorts'], - 'tabs': tabs[1:] - }) - return render_to_response("noel/insidepage.html", context) - - -def get_tab(request): - tab_slug = request.GET.get("tab", "") - tab = get_object_or_404(ModuleTab, slug=tab_slug) - return render_to_json_response(tab.get_dict()) - - - -def get_list(request): - numre = re.compile("[0-9]*") - tab_slug = request.GET.get("tab", "") - tab = get_object_or_404(ModuleTab, slug=tab_slug) - object_id = request.GET.get("object_id", False) - object_id = object_id[numre.match(object_id).start():numre.match(object_id).end()] #FIXME - why is the front-end sending this? - sortString = request.GET.get("sort", "") - if sortString == "" or sortString == 'null': - sortArray = [] - else: - sortOperator = sortString[0] - sortField = sortString[1:] - sortArray = [{ - 'operator': sortOperator, - 'key': sortField - }] - - list_options = { - 'search': request.GET.get("search", ""), - 'sort': sortArray, - 'page': request.GET.get("page", 1), - 'object_id': object_id, - 'count': request.GET.get("count", 15) #FIXME: make list_length either in settings.py or config per model - } - object_list = tab.get_list(list_options) - return render_to_json_response(object_list) - - -def get_details(request): -# import pdb - tab_slug = request.GET.get("tab", '') - tab = get_object_or_404(ModuleTab, slug=tab_slug) - model_class = tab.model_class() - object_id = request.GET.get("object_id", 0) - obj = get_object_or_404(model_class, pk=object_id) - ret = obj.insidepage_dict(request) -# ret['csrf_token'] = c -# pdb.set_trace() - return render_to_json_response(ret) - +''' +Main function that handles incoming requests: + http:///m///?object_id=&search=&sort=&page=&count= + All options / parameters after are optional. +''' def render_object(request, module_slug): + #Get the module from module slug in URL, else return a 404 module = get_object_or_404(Module, slug=module_slug) + + #If there is a tab slug in the URL, fetch that tab, else fetch the default tab, else get the first of all tabs tab_slug = request.GET.get("tab", '') try: tab = get_object_or_404(ModuleTab, slug=tab_slug) @@ -91,7 +24,10 @@ def render_object(request, module_slug): except: tab = module.moduletab_set.all()[0] + #Get the model class associated with the tab model_class = tab.model_class() + + #Get various options from GET params object_id = request.GET.get("object_id", 0) sortString = request.GET.get("sort", "") if sortString == "" or sortString == 'null': @@ -109,16 +45,20 @@ def render_object(request, module_slug): 'page': request.GET.get("page", 1), 'object_id': object_id, 'count': request.GET.get("count", 15) #FIXME: make list_length either in settings.py or config per model - } + } + + #Call get_list method on the tab to get a list of all items in the list object_list = tab.get_list(list_options) + #If no object_id was provided, select the first object in the list if object_id == 0: object_id = object_list['items'][0]['id'] + #Fetch the object whose details to display - if the result list is empty, handle displaying a No Results page to the user if object_list['has_results']: obj = get_object_or_404(model_class, pk=object_id) item_data = obj.insidepage_dict(request) - else: #Should be a better way to tell the template a search returned no result + else: #We create a 'fake' object with properties that we require the template to render No results found, FIXME: should be a cleaner way to perhaps render a separate template with the list of items on the left and saying "No Results Found" - however, this works for now. obj = { 'get_title': 'No Results Found', 'get_absolute_url': tab.get_absolute_url() @@ -127,11 +67,92 @@ def render_object(request, module_slug): 'html': 'The search query you entered did not return any results.' } + #Get all the context and render to template context = RequestContext(request, { - 'item': obj, - 'item_data': item_data, - 'tab': tab, - 'object_list': object_list, - 'tabs': tab.module.moduletab_set.all() + 'item': obj, #The object to be rendered + 'item_data': item_data, #custom object data got from the .insidepage_dict method on the model class + 'tab': tab, #the tab object + 'object_list': object_list, #Pagination object, list of items for left list. + 'tabs': tab.module.moduletab_set.all() #All the tabs of the current module }) return render_to_response("noel/render_object.html", context) + +###### +#HERE BE OLD DISCARED CODE: +##### +#def main(request, module_slug): +# m = get_object_or_404(Module, slug=module_slug) +# tabs = m.moduletab_set.all().order_by('order') +# default_tab = tabs[0] +# try: +# formStr = default_tab.model_class().add_form +# has_add = True +# add_form_class = default_tab.model_class().get_add_form() +# add_form = add_form_class() +# except: +# add_form = None +# has_add = False + +# list_options = {} #get some options as GET params, etc. to potentially pass to get_list +# default_tab_list = default_tab.model_class().get_list(list_options) +# context = RequestContext(request, { +# 'title': m.title, +# 'about': m.about, +# 'has_add': has_add, +# 'add_form': add_form, +# 'default_tab': tabs[0], +# 'default_list': default_tab_list, +# 'default_sorts': default_tab.get_dict()['sorts'], +# 'tabs': tabs[1:] +# }) +# return render_to_response("noel/insidepage.html", context) + + +#def get_tab(request): +# tab_slug = request.GET.get("tab", "") +# tab = get_object_or_404(ModuleTab, slug=tab_slug) +# return render_to_json_response(tab.get_dict()) +# + + +#def get_list(request): +# numre = re.compile("[0-9]*") +# tab_slug = request.GET.get("tab", "") +# tab = get_object_or_404(ModuleTab, slug=tab_slug) +# object_id = request.GET.get("object_id", False) +# object_id = object_id[numre.match(object_id).start():numre.match(object_id).end()] #FIXME - why is the front-end sending this? +# sortString = request.GET.get("sort", "") +# if sortString == "" or sortString == 'null': +# sortArray = [] +# else: +# sortOperator = sortString[0] +# sortField = sortString[1:] +# sortArray = [{ +# 'operator': sortOperator, +# 'key': sortField +# }] + +# list_options = { +# 'search': request.GET.get("search", ""), +# 'sort': sortArray, +# 'page': request.GET.get("page", 1), +# 'object_id': object_id, +# 'count': request.GET.get("count", 15) #FIXME: make list_length either in settings.py or config per model +# } +# object_list = tab.get_list(list_options) +# return render_to_json_response(object_list) + + +#def get_details(request): +## import pdb +# tab_slug = request.GET.get("tab", '') +# tab = get_object_or_404(ModuleTab, slug=tab_slug) +# model_class = tab.model_class() +# object_id = request.GET.get("object_id", 0) +# obj = get_object_or_404(model_class, pk=object_id) +# ret = obj.insidepage_dict(request) +## ret['csrf_token'] = c +## pdb.set_trace() +# return render_to_json_response(ret) + +