Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Integrate supplementary fields to base field #181

Merged
merged 1 commit into from

3 participants

@bruth
Owner

This adds support for defining an explicit label, search, code,
and order field that exists on the same model of the base field. This
provides a more dynamic nature to a data field by having it be represented
in various ways depending on how it is being interacted with.

This implementation supersedes the need for direct integration of the
Lexicon and ObjectSet classes in the DataField API. Detection of these
types will be removed in 2.4, however the detection during the avocado
init will remain for now.

@bruth bruth Integrate supplementary fields to base field
This adds support for defining an explicit label, search, code,
and order field that exists on the same model of the base field. This
provides a more dynamic nature to a data field by having it be represented
in various ways depending on how it is being interacted with.

This implementation supersedes the need for direct integration of the
Lexicon and ObjectSet classes in the DataField API. Detection of these
types will be removed in 2.4, however the detection during the avocado
init will remain for now.
88c4995
@coveralls

Coverage Status

Coverage remained the same when pulling 88c4995 on supp-fields into 0714a16 on 2.3.

@naegelyd naegelyd merged commit 889aafc into from
@naegelyd naegelyd deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 26, 2014
  1. @bruth

    Integrate supplementary fields to base field

    bruth authored
    This adds support for defining an explicit label, search, code,
    and order field that exists on the same model of the base field. This
    provides a more dynamic nature to a data field by having it be represented
    in various ways depending on how it is being interacted with.
    
    This implementation supersedes the need for direct integration of the
    Lexicon and ObjectSet classes in the DataField API. Detection of these
    types will be removed in 2.4, however the detection during the avocado
    init will remain for now.
This page is out of date. Refresh to see the latest.
View
62 avocado/admin.py
@@ -1,8 +1,6 @@
from django.db import transaction
from django.contrib import admin
-from django.contrib.admin import SimpleListFilter
from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext_lazy as _
from avocado.models import DataField, DataConcept, DataCategory, \
DataConceptField, DataView, DataContext, DataQuery
from avocado.forms import DataFieldAdminForm
@@ -53,63 +51,15 @@ def mark_unarchived(self, request, queryset):
mark_unarchived.short_description = 'Unarchive'
-class LexiconListFilter(SimpleListFilter):
- title = _('lexicon')
-
- parameter_name = 'lexicon'
-
- def lookups(self, request, model_admin):
- return (
- ('1', _('Yes')),
- ('0', _('No')),
- )
-
- def queryset(self, request, queryset):
- value = self.value()
-
- if value:
- subquery = queryset.only('app_name', 'model_name', 'field_name')
- ids = [x.pk for x in subquery if x.lexicon]
- if value == '1':
- queryset = queryset.filter(id__in=ids)
- else:
- queryset = queryset.exclude(id__in=ids)
- return queryset
-
-
-class ObjectSetListFilter(SimpleListFilter):
- title = _('objectset')
-
- parameter_name = 'objectset'
-
- def lookups(self, request, model_admin):
- return (
- ('1', _('Yes')),
- ('0', _('No')),
- )
-
- def queryset(self, request, queryset):
- value = self.value()
-
- if value:
- subquery = queryset.only('app_name', 'model_name', 'field_name')
- ids = [x.pk for x in subquery if x.objectset]
- if value == '1':
- queryset = queryset.filter(id__in=ids)
- else:
- queryset = queryset.exclude(id__in=ids)
- return queryset
-
-
class DataFieldAdmin(PublishedAdmin):
form = DataFieldAdminForm
list_display = ('name', 'published', 'archived', 'internal',
'orphan_status', 'model_name', 'enumerable',
- 'is_lexicon', 'is_objectset', 'related_dataconcepts')
+ 'related_dataconcepts')
list_filter = ('published', 'archived', 'internal', 'model_name',
- 'enumerable', LexiconListFilter, ObjectSetListFilter)
+ 'enumerable')
list_editable = ('published', 'archived', 'internal', 'enumerable')
@@ -167,14 +117,6 @@ def orphan_status(self, obj):
return 'OK'
orphan_status.short_description = 'Orphan Status'
- def is_lexicon(self, obj):
- return obj.lexicon
- is_lexicon.short_description = 'Is Lexicon'
-
- def is_objectset(self, obj):
- return obj.objectset
- is_objectset.short_description = 'Is ObjectSet'
-
def related_dataconcepts(self, obj):
queryset = obj.concepts.only('id', 'name')
reverse_name = 'admin:avocado_dataconcept_change'
View
2  avocado/export/_r.py
@@ -67,7 +67,7 @@ def write(self, iterable, buff=None, template_name='export/script.R',
labels.append(u'attr(data${0}, "label") = "{1}"'.format(
name, unicode(cfield)))
- if field.lexicon:
+ if field.code_field:
codes = self._code_values(name, field)
factors.append(codes[0])
levels.append(codes[1])
View
2  avocado/export/_sas.py
@@ -109,7 +109,7 @@ def write(self, iterable, buff=None, template_name='export/script.sas',
# If a field can be coded create a SAS PROC Format statement
# that creates a value dictionary
- if field.lexicon:
+ if field.code_field:
value_format, value = self._code_values(name, field)
value_formats.append(value_format)
values.append(value)
View
16 avocado/management/subcommands/init.py
@@ -128,6 +128,7 @@ def handle(self, *args, **options):
for model in pending_models:
lexicon = issubclass(model, Lexicon)
+
if dep_supported('objectset'):
from objectset.models import ObjectSet
objectset = issubclass(model, ObjectSet)
@@ -183,9 +184,11 @@ def handle_field(self, field, model_name, app_name, **options):
else:
objectset = False
+ lexicon = issubclass(field.model, Lexicon)
+
# Lexicons and ObjectSets are represented via their primary key, so
# these may pass
- if not objectset and not issubclass(field.model, Lexicon):
+ if not objectset and not lexicon:
# Check for primary key, and foreign key fields
if isinstance(field, self.key_field_types) and not include_keys:
print(u'({0}) {1}.{2} is a primary or foreign key. Skipping...'
@@ -215,6 +218,17 @@ def handle_field(self, field, model_name, app_name, **options):
'field_name': field.name,
}
+ if lexicon:
+ kwargs.update({
+ 'label_field_name': 'label',
+ 'order_field_name': 'order',
+ 'code_field_name': 'code',
+ })
+ elif objectset and hasattr(field.model, 'label_field'):
+ kwargs.update({
+ 'label_field_name': field.model.label_field
+ })
+
try:
f = DataField.objects.get(**lookup)
except DataField.DoesNotExist:
View
249 avocado/migrations/0032_auto__add_field_datafield_label_field_name__add_field_datafield_search.py
@@ -0,0 +1,249 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'DataField.label_field_name'
+ db.add_column(u'avocado_datafield', 'label_field_name',
+ self.gf('django.db.models.fields.CharField')(max_length=200, null=True),
+ keep_default=False)
+
+ # Adding field 'DataField.search_field_name'
+ db.add_column(u'avocado_datafield', 'search_field_name',
+ self.gf('django.db.models.fields.CharField')(max_length=200, null=True),
+ keep_default=False)
+
+ # Adding field 'DataField.order_field_name'
+ db.add_column(u'avocado_datafield', 'order_field_name',
+ self.gf('django.db.models.fields.CharField')(max_length=200, null=True),
+ keep_default=False)
+
+ # Adding field 'DataField.code_field_name'
+ db.add_column(u'avocado_datafield', 'code_field_name',
+ self.gf('django.db.models.fields.CharField')(max_length=200, null=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'DataField.label_field_name'
+ db.delete_column(u'avocado_datafield', 'label_field_name')
+
+ # Deleting field 'DataField.search_field_name'
+ db.delete_column(u'avocado_datafield', 'search_field_name')
+
+ # Deleting field 'DataField.order_field_name'
+ db.delete_column(u'avocado_datafield', 'order_field_name')
+
+ # Deleting field 'DataField.code_field_name'
+ db.delete_column(u'avocado_datafield', 'code_field_name')
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'avocado.datacategory': {
+ 'Meta': {'ordering': "('parent__order', 'parent__name', 'order', 'name')", 'object_name': 'DataCategory'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.FloatField', [], {'null': 'True', 'db_column': "'_order'", 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['avocado.DataCategory']"}),
+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'avocado.dataconcept': {
+ 'Meta': {'ordering': "('category__order', 'category__name', 'order', 'name')", 'object_name': 'DataConcept'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['avocado.DataCategory']", 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'concepts'", 'symmetrical': 'False', 'through': u"orm['avocado.DataConceptField']", 'to': u"orm['avocado.DataField']"}),
+ 'formatter_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'concepts+'", 'null': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ident': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.FloatField', [], {'null': 'True', 'db_column': "'_order'", 'blank': 'True'}),
+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'queryable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'concepts+'", 'blank': 'True', 'to': u"orm['sites.Site']"}),
+ 'sortable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'viewable': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'avocado.dataconceptfield': {
+ 'Meta': {'ordering': "('order', 'name')", 'object_name': 'DataConceptField'},
+ 'concept': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'concept_fields'", 'to': "orm['avocado.DataConcept']"}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'field': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'concept_fields'", 'to': u"orm['avocado.DataField']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.FloatField', [], {'null': 'True', 'db_column': "'_order'", 'blank': 'True'})
+ },
+ u'avocado.datacontext': {
+ 'Meta': {'object_name': 'DataContext'},
+ 'accessed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 2, 25, 0, 0)'}),
+ 'count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_column': "'_count'"}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'json': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'forks'", 'null': 'True', 'to': u"orm['avocado.DataContext']"}),
+ 'session': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'template': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'tree': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'datacontext+'", 'null': 'True', 'to': u"orm['auth.User']"})
+ },
+ u'avocado.datafield': {
+ 'Meta': {'ordering': "('category__order', 'category__name', 'order', 'name')", 'unique_together': "(('app_name', 'model_name', 'field_name'),)", 'object_name': 'DataField'},
+ 'app_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['avocado.DataCategory']", 'null': 'True', 'blank': 'True'}),
+ 'code_field_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'data_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'enumerable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'field_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'fields+'", 'null': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'label_field_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+ 'model_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.FloatField', [], {'null': 'True', 'db_column': "'_order'", 'blank': 'True'}),
+ 'order_field_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'search_field_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'fields+'", 'blank': 'True', 'to': u"orm['sites.Site']"}),
+ 'translator': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'unit': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+ 'unit_plural': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'})
+ },
+ u'avocado.dataquery': {
+ 'Meta': {'object_name': 'DataQuery'},
+ 'accessed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'context_json': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'distinct_count': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'forks'", 'null': 'True', 'to': u"orm['avocado.DataQuery']"}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'record_count': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'session': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'shared_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shareddataquery+'", 'symmetrical': 'False', 'to': u"orm['auth.User']"}),
+ 'template': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'tree': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'dataquery+'", 'null': 'True', 'to': u"orm['auth.User']"}),
+ 'view_json': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'})
+ },
+ u'avocado.dataview': {
+ 'Meta': {'object_name': 'DataView'},
+ 'accessed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 2, 25, 0, 0)'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'json': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'forks'", 'null': 'True', 'to': u"orm['avocado.DataView']"}),
+ 'session': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'template': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'dataview+'", 'null': 'True', 'to': u"orm['auth.User']"})
+ },
+ 'avocado.log': {
+ 'Meta': {'object_name': 'Log'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
+ 'event': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': u"orm['auth.User']"})
+ },
+ 'avocado.revision': {
+ 'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Revision'},
+ 'changes': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ 'data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+revision'", 'null': 'True', 'to': u"orm['auth.User']"})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['avocado']
View
269 avocado/models.py
@@ -1,5 +1,10 @@
import re
+from warnings import warn
from datetime import datetime
+try:
+ from collections import OrderedDict
+except ImportError:
+ from ordereddict import OrderedDict
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.auth.models import User, Group
@@ -33,6 +38,36 @@
'invalid')
+def is_lexicon(f):
+ """Returns true if the model is a subclass of Lexicon and this
+ is the pk field. All other fields on the class are treated as
+ normal datafields.
+ """
+ warn('Lexicon detection in the DataField API is deprecated and '
+ 'will be removed in 2.4. Set the alternate fields explicitly '
+ 'on the field instance', DeprecationWarning)
+ return f.model and issubclass(f.model, Lexicon) \
+ and f.field == f.model._meta.pk
+
+
+def is_objectset(f):
+ """Returns true if the model is a subclass of ObjectSet and this
+ is the pk field. All other fields on the class are treated as
+ normal datafields.
+ """
+ warn('ObjectSet detection in the DataField API is deprecated and '
+ 'will be removed in 2.4. Set the alternate fields explicitly '
+ 'on the field instance', DeprecationWarning)
+
+ if dep_supported('objectset'):
+ from objectset.models import ObjectSet
+
+ return f.model and issubclass(f.model, ObjectSet) \
+ and f.field == f.model._meta.pk
+
+ return False
+
+
class DataCategory(Base, PublishArchiveMixin):
"A high-level organization for data concepts."
# A reference to a parent for hierarchical categories
@@ -55,13 +90,18 @@ class DataField(BasePlural, PublishArchiveMixin):
it defines the natural key of the Django field that represents the location
of that data e.g. ``library.book.title``.
"""
+ # App/model/field represent the natural key of this field based on
+ # Django's methods of distinguishing models.
app_name = models.CharField(max_length=200)
model_name = models.CharField(max_length=200)
field_name = models.CharField(max_length=200)
- internal = models.BooleanField(default=False, help_text='Flag for '
- 'internal use and does not abide by the '
- 'published and archived rules.')
+ # Supplementary fields that respresent alternate representations
+ # of the base field
+ label_field_name = models.CharField(max_length=200, null=True)
+ search_field_name = models.CharField(max_length=200, null=True)
+ order_field_name = models.CharField(max_length=200, null=True)
+ code_field_name = models.CharField(max_length=200, null=True)
# An optional unit for this field's data. In some cases databases may have
# a separate column which denotes the unit for another column, but this is
@@ -108,6 +148,10 @@ class DataField(BasePlural, PublishArchiveMixin):
# The order of this datafield with respect to the category (if defined).
order = models.FloatField(null=True, blank=True, db_column='_order')
+ internal = models.BooleanField(default=False, help_text='Flag for '
+ 'internal use and does not abide by the '
+ 'published and archived rules.')
+
objects = managers.DataFieldManager()
class Meta(object):
@@ -168,8 +212,6 @@ def init(cls, app_name, model_name=None, field_name=None, **kwargs):
def __unicode__(self):
if self.name:
return self.name
- if self.lexicon or self.objectset:
- return self.model._meta.verbose_name
return u'{0} {1}'.format(self.model._meta.verbose_name,
self.field.verbose_name).title()
@@ -233,6 +275,90 @@ def field(self):
return self.real_field
@property
+ def value_field(self):
+ "Alias for field."
+ return self.field
+
+ @property
+ def label_field(self):
+ "Returns the label field object for this datafield."
+ model = self.model
+
+ if model:
+ field_name = None
+
+ if self.label_field_name:
+ field_name = self.label_field_name
+ elif is_lexicon(self):
+ field_name = 'label'
+ elif is_objectset(self):
+ if hasattr(self.model, 'label_field'):
+ field_name = self.model.label_field
+ else:
+ field_name = 'pk'
+
+ if field_name:
+ try:
+ return model._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ pass
+
+ return self.field
+
+ @property
+ def search_field(self):
+ "Returns the search field object for this datafield."
+ model = self.model
+
+ if model and self.search_field_name:
+ try:
+ return model._meta.get_field(self.search_field_name)
+ except FieldDoesNotExist:
+ pass
+
+ return self.label_field
+
+ @property
+ def order_field(self):
+ "Returns the order field object for this datafield."
+ model = self.model
+
+ if model:
+ field_name = None
+
+ if self.order_field_name:
+ field_name = self.order_field_name
+ elif is_lexicon(self):
+ field_name = 'order'
+
+ if field_name:
+ try:
+ return model._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ pass
+
+ return self.field
+
+ @property
+ def code_field(self):
+ "Returns the code field object for this datafield."
+ model = self.model
+
+ if model:
+ field_name = None
+
+ if self.code_field_name:
+ field_name = self.code_field_name
+ elif is_lexicon(self):
+ field_name = 'code'
+
+ if field_name:
+ try:
+ return model._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ pass
+
+ @property
def nullable(self):
"Returns whether this field can contain NULL values."
return self.field.null
@@ -252,50 +378,46 @@ def simple_type(self):
return utils.get_simple_type(self.field)
@property
- def lexicon(self):
- """Returns true if the model is a subclass of Lexicon and this
- is the pk field. All other fields on the class are treated as
- normal datafields.
- """
- return self.model and issubclass(self.model, Lexicon) \
- and self.field == self.model._meta.pk
-
- @property
- def objectset(self):
- """Returns true if the model is a subclass of ObjectSet and this
- is the pk field. All other fields on the class are treated as
- normal datafields.
- """
- if dep_supported('objectset'):
- from objectset.models import ObjectSet
-
- return self.model and issubclass(self.model, ObjectSet) \
- and self.field == self.model._meta.pk
-
- return False
-
- @property
def searchable(self):
"Returns true if a text-field and is not an enumerable field."
- return self.simple_type == 'string' and not self.enumerable
+ search_field = self.search_field
+ return utils.simple_type(search_field) == 'string' \
+ and not self.enumerable
# Convenience Methods
# Easier access to the underlying data for this data field
def values_list(self):
"Returns a `ValuesListQuerySet` of values for this field."
- if self.lexicon or self.objectset:
- return self.model.objects.values_list('pk', flat=True)
- return self.model.objects.values_list(self.field_name, flat=True)\
- .order_by(self.field_name).distinct()
+ value_field = self.value_field.name
+ order_field = self.order_field.name
+
+ return self.model.objects.values_list(value_field, flat=True)\
+ .order_by(order_field).distinct()
+
+ def labels_list(self):
+ "Returns a `ValuesListQuerySet` of labels for this field."
+ label_field = self.label_field.name
+ order_field = self.order_field.name
+
+ return self.model.objects.values_list(label_field, flat=True)\
+ .order_by(order_field).distinct()
+
+ def codes_list(self):
+ "Returns a `ValuesListQuerySet` of labels for this field."
+ if not self.code_field:
+ return
+
+ code_field = self.code_field.name
+ order_field = self.order_field.name
+
+ return self.model.objects.values_list(code_field, flat=True)\
+ .order_by(order_field).distinct()
def search(self, query):
"Rudimentary search for string-based values."
- if self.simple_type == 'string' or self.lexicon:
- if self.lexicon:
- field_name = 'value'
- else:
- field_name = self.field_name
+ if self.searchable:
+ field_name = self.search_field.name
filters = {u'{0}__icontains'.format(field_name): query}
return self.values_list().filter(**filters)
@@ -309,23 +431,13 @@ def get_plural_unit(self):
return plural
def get_label(self, value):
- """Gets the label for a particular raw data value.
- If this is classified as `searchable`, a database hit will occur.
- """
- if self.searchable:
- kwargs = {self.field_name: value}
- if self.lexicon:
- return self.model.objects.filter(**kwargs)\
- .values_list('label', flat=True)[0]
- if self.objectset:
- if hasattr(self.model, 'label_field'):
- field = self.model.label_field
- else:
- field = 'pk'
- return self.model.objects.filter(**kwargs)\
- .values_list(field, flat=True)[0]
- return smart_unicode(value)
- return dict(self.choices()).get(value, smart_unicode(value))
+ "Get the corresponding label to a value."
+ labels = self.value_labels()
+
+ if value in labels:
+ return labels[value]
+
+ return smart_unicode(value)
# Data-related Cached Properties
# These may be cached until the underlying data changes
@@ -336,46 +448,37 @@ def size(self):
@cached_method(version='data_version')
def values(self):
- "Returns a distinct list of the values."
+ "Returns a distinct list of values."
return tuple(self.values_list())
@cached_method(version='data_version')
def labels(self):
- """Returns an ordered set of labels corresponding to the values.
- If this field represents to a Lexicon subclass, the `label` field
- will be used, otherwise the values will simply be unicoded.
- """
- if self.lexicon:
- return tuple(self.model.objects.values_list('label', flat=True))
- if self.objectset:
- if hasattr(self.model, 'label_field'):
- field = self.model.label_field
- else:
- field = 'pk'
- return tuple(self.model.objects.values_list(field, flat=True))
- # Unicode each value, use an iterator here to prevent loading the
- # raw values in memory
- return map(smart_unicode, iter(self.values_list()))
+ "Returns a distinct list of labels."
+ return tuple(self.labels_list())
@cached_method(version='data_version')
def codes(self):
"Returns a distinct set of coded values for this field"
- if self.lexicon:
- return tuple(self.model.objects.values_list('code', flat=True))
+ if self.code_field:
+ return tuple(self.codes_list())
- def choices(self):
- "Returns a distinct set of choices for this field."
- return zip(self.values(), self.labels())
+ def value_labels(self):
+ "Returns a distinct set of value/label pairs for this field."
+ return OrderedDict(zip(self.values(), self.labels()))
- def coded_choices(self):
- "Returns a distinct set of coded choices for this field."
- if self.lexicon:
- return zip(self.codes(), self.labels())
+ def coded_labels(self):
+ "Returns a distinct set of code/label pairs for this field."
+ if self.code_field:
+ return OrderedDict(zip(self.codes(), self.labels()))
def coded_values(self):
- "Returns a distinct set of coded values for this field."
- if self.lexicon:
- return zip(self.values(), self.codes())
+ "Returns a distinct set of value/code pairs for this field."
+ if self.code_field:
+ return OrderedDict(zip(self.values(), self.codes()))
+
+ # Alias since it's common parlance in Django
+ choices = value_labels
+ coded_choices = coded_labels
# Data Aggregation Properties
def groupby(self, *args):
View
24 avocado/query/oldparsers/dataview.py
@@ -31,9 +31,11 @@ def concept_ids(self):
def ordering(self):
ids = []
length = len(self.facets)
+
for facet in self.facets:
if facet.get('enabled') is False:
continue
+
if facet.get('sort') in SORT_DIRECTIONS:
# If sort is not defined, default to length of facets
ids.append((
@@ -41,6 +43,7 @@ def ordering(self):
facet['concept'],
facet['sort'],
))
+
# Sort relative to sort index
ids.sort(key=lambda x: x[0])
# Return only the concept id and sort direction
@@ -60,8 +63,10 @@ def _get_concepts(self, ids):
def _get_fields_for_concepts(self, ids):
"Returns an ordered list of fields for concept `ids`."
from avocado.models import DataConceptField
+
if not ids:
return OrderedDict()
+
# Concept fields that are sorted by concept then order, but are not
# in the original order defined in `ids`
cfields = list(DataConceptField.objects.filter(concept__pk__in=ids)
@@ -73,11 +78,15 @@ def _get_fields_for_concepts(self, ids):
# Construct an ordered dict of fields by their concept
groups = OrderedDict()
+
for cf in cfields:
pk = cf.concept.pk
+
if pk not in groups:
groups[pk] = []
+
groups[pk].append(cf.field)
+
return groups
def _get_select(self, distinct):
@@ -89,6 +98,7 @@ def _get_select(self, distinct):
# this by removing redundant rows relative to the *original* columns.
ids = list(self.concept_ids)
ordering = self.ordering
+
if ordering and distinct:
ids += list(zip(*ordering)[0])
@@ -99,10 +109,8 @@ def _get_select(self, distinct):
model_fields = []
for f in fields:
- if f.lexicon:
- model_fields.append(f.model._meta.get_field('label'))
- else:
- model_fields.append(f.field)
+ model_fields.append(f.label_field)
+
return model_fields
def _get_order_by(self):
@@ -117,13 +125,7 @@ def _get_order_by(self):
for pk, direction in ordering:
for f in groups[pk]:
- # Special case for Lexicon-based models, order by their
- # model-defined `order` field.
- if f.lexicon:
- field = f.model._meta.get_field('order')
- lookup = tree.query_string_for_field(field)
- else:
- lookup = tree.query_string_for_field(f.field)
+ lookup = tree.query_string_for_field(f.order_field)
if direction.lower() == 'desc':
order_by.append('-' + lookup)
View
2  tests/cases/lexicon/tests.py
@@ -8,7 +8,6 @@ class LexiconTestCase(TestCase):
def test_datafield(self):
f = DataField(app_name='tests', model_name='month', field_name='id')
- self.assertTrue(f.lexicon)
self.assertEqual(f.values(), (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
self.assertEqual(f.labels(), (u'January', u'February', u'March',
u'April', u'May', u'June', u'July', u'August', u'September',
@@ -17,7 +16,6 @@ def test_datafield(self):
def test_foreign_key_datafield(self):
f = DataField(app_name='tests', model_name='date', field_name='month')
- self.assertTrue(f.lexicon)
self.assertEqual(f.model, Month)
self.assertEqual(f.field, Month._meta.pk)
self.assertEqual(f.values(), (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
Something went wrong with that request. Please try again.