blob: c042a8b45a7347c44b7ba36400ce2e0e64f12a04 [file] [log] [blame]
# Copyright 2011 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module containing the classes which lets us construct readonly
pages for models.
"""
import collections
import re
from django.template import loader
from django.utils.datastructures import SortedDict
from soc.views.helper.surveys import SurveySchema
class ModelReadOnlyTemplateOptions(object):
"""Class holding options specified in the Meta class of the model template.
Holds the following options:
model: an Appengine db.Model class, or None
css_prefix: the prefix that needs to be added to CSS attributes
while rendering
fields: list of appengine model property names to be rendered in the
template, or None
hidden_fields: list of appengine model property names to be rendered in the
template with 'display: none' style, or None
exclude: list of appengine model property names to be skipped from the
template, or None
renderers: dictionary of field name and function pairs where the supplied
function specifies how the field should be rendered.
"""
def __init__(self, options=None):
"""
"""
self.model = getattr(options, 'model', None)
self.css_prefix = getattr(options, 'css_prefix',
getattr(self.model, '__name__', None))
self.fields = getattr(options, 'fields', None)
self.hidden_fields = getattr(options, 'hidden_fields', None)
self.exclude = getattr(options, 'exclude', None)
self.renderers = getattr(options, 'renderers', None)
class ModelReadOnlyTemplateMetaclass(type):
"""Meta class that alters the creation of model readonly template classes.
During the creation of the model readonly template class we filter out
the needed and unnecessary properties from the model using the attributes
specified in the Meta class of the model readonly template class.
"""
def __new__(cls, class_name, bases, dict):
"""Method that alters the class attributes in the model readonly template.
This method alters the creation of the object before initialization by
adding in the model's attributes that needs to be rendered. It also
adds other necessary options extracted from the Meta class inside the
model's readonly template class.
"""
opts = ModelReadOnlyTemplateOptions(dict.get('Meta', None))
if opts.model is not None:
model_fields = SortedDict()
model_hidden_fields = {}
for name, prop in sorted(opts.model.properties().iteritems(),
key=lambda prop: prop[1].creation_counter):
if opts.fields and name not in opts.fields:
if opts.hidden_fields and name in opts.hidden_fields:
model_hidden_fields[name] = prop
continue
if opts.exclude and name in opts.exclude:
continue
model_fields[name] = prop
dict['fields'] = model_fields
dict['hidden_fields'] = model_hidden_fields
dict['renderers'] = opts.renderers or {}
if opts.css_prefix:
dict['css_prefix'] = opts.css_prefix
return super(ModelReadOnlyTemplateMetaclass, cls).__new__(
cls, class_name, bases, dict)
class ModelReadOnlyTemplate(object):
"""A base class that constructs the readonly template for a given model.
This uses the same notion that Django's forms APIs use to generate forms
for given models. The idea is completely inspired from Django's ModelForm
APIs and tries to mimic the same names that is used there in order to
provide consistency.
In addition, to provide more consistency with the Melange's framework
itself we also add a render method that render this class into an html
template.
"""
__metaclass__ = ModelReadOnlyTemplateMetaclass
def __init__(self, instance=None):
"""Constructor to initialize the model instance.
The readonly template will be rendered for the data in this model instance.
"""
self.instance = instance
for name, field in self.hidden_fields.items():
self.hidden_fields[name] = getattr(self.instance, name)
def __iter__(self):
"""Iterator yielding groups of model instance's properties to be rendered.
"""
grouping = collections.defaultdict(list)
for name, field in self.fields.items():
group = getattr(field, 'group', '0. ')
grouping[group].append((field.verbose_name, getattr(
self.instance, name)))
rexp = re.compile(r"\d+. ")
for group, fields in sorted(grouping.items()):
yield rexp.sub('', group), fields
def render(self):
"""Renders the html collecting the attributes available in this class.
"""
context = {
'model': self,
'hidden_fields': self.hidden_fields,
'css_prefix': self.css_prefix,
}
rendered = loader.render_to_string(self.template_path,
dictionary=context)
return rendered
class SurveyRecordReadOnlyTemplate(ModelReadOnlyTemplate):
"""A base class that constructs the readonly template for given survey record.
This uses the same notion that is used to build the model based readonly
templates but the schema read from the survey schema rather than the model.
"""
template_path = 'modules/gsoc/_survey/readonly_template.html'
def __init__(self, instance=None):
"""Constructor to initialize the model instance.
The readonly template will be rendered for the data in this model instance.
"""
self.instance = instance
self.schema = None
if self.instance:
self.schema = SurveySchema(self.instance.survey)
def fieldsIterator(self):
"""Iterates through the fields that were declared for this template.
Yields:
a pair whose first element is a verbose name of a model field and
the other element is the value of that field.
"""
for name, field in self.fields.items():
renderer = self.renderers.get(name)
if renderer:
value = renderer(self.instance)
else:
value = getattr(self.instance, name)
yield field.verbose_name, value
def schemaIterator(self):
"""Iterates through the survey schema questions.
Yields:
a pair whose first element is a label for a question belonging to
the schema and the other element is the answer for that question.
"""
if self.schema:
for field in self.schema:
field_id = field.getFieldName()
label = field.getLabel()
value = getattr(self.instance, field_id, '-')
if isinstance(value, list):
value = ', '.join(value)
yield label, value
def render(self):
"""Renders the html collecting the attributes available in this class.
"""
context = {
'record': self,
'hidden_fields': self.hidden_fields,
'css_prefix': self.css_prefix,
}
meta = getattr(self, 'Meta', None)
context['survey_name'] = getattr(meta, 'survey_name', '')
rendered = loader.render_to_string(self.template_path,
dictionary=context)
return rendered