added many comments, much code cleanup

This commit is contained in:
Sanj 2012-06-14 20:41:39 +05:30
parent 08e9398ab3
commit 3d8de06494
3 changed files with 334 additions and 247 deletions

View File

@ -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 <app>/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/<app_label>/<model_name>.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

View File

@ -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<module_slug>.*)/$', views.render_object),
(r'^(?P<module_slug>.*)/$', views.render_object),
)

View File

@ -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://<website>/m/<module_name>/<tab_name>/?object_id=<x>&search=<search_term>&sort=<sort_string>&page=<page_no>&count=<items_per_page>
All options / parameters after <module_name> 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)