From: Stephen Burrows Date: Thu, 10 Feb 2011 17:29:13 +0000 (-0500) Subject: Removed location models and subclass limiting in favor of a ContentTypeRegistryLimite... X-Git-Tag: philo-0.9~15^2~3^2~11 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/cf3eea2d358763b2408149b620a1b61dabdb228d Removed location models and subclass limiting in favor of a ContentTypeRegistryLimiter. Added admins and initial migrations. --- diff --git a/contrib/julian/admin.py b/contrib/julian/admin.py new file mode 100644 index 0000000..a822e1d --- /dev/null +++ b/contrib/julian/admin.py @@ -0,0 +1,33 @@ +from django.contrib import admin +from philo.admin import EntityAdmin +from philo.contrib.julian.models import Location, Event, Calendar + + +class LocationAdmin(EntityAdmin): + pass + + +class EventAdmin(EntityAdmin): + fieldsets = ( + (None, { + 'fields': ('title', 'description', 'tags', 'parent_event', 'owner') + }), + ('Location', { + 'fields': ('location_content_type', 'location_pk') + }), + ('Time', { + 'fields': (('start_date', 'start_time'), ('end_date', 'end_time'),), + }) + ) + related_lookup_fields = { + 'generic': [["location_content_type", "location_pk"]] + } + + +class CalendarAdmin(EntityAdmin): + pass + + +admin.site.register(Location, LocationAdmin) +admin.site.register(Event, EventAdmin) +admin.site.register(Calendar, CalendarAdmin) \ No newline at end of file diff --git a/contrib/julian/fields.py b/contrib/julian/fields.py deleted file mode 100644 index cbb1a41..0000000 --- a/contrib/julian/fields.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.contrib.localflavor.us.forms import USZipCodeField as USZipCodeFormField -from django.core.validators import RegexValidator -from django.db import models - - -class USZipCodeField(models.CharField): - default_validators = [RegexValidator(r'^\d{5}(?:-\d{4})?$')] - - def __init__(self, *args, **kwargs): - kwargs['max_length'] = 10 - super(USZipCodeField, self).__init__(*args, **kwargs) - - def formfield(self, form_class=USZipCodeFormField, **kwargs): - return super(USZipCodeField, self).formfield(form_class, **kwargs) - - -try: - from south.modelsinspector import add_introspection_rules -except ImportError: - pass -else: - add_introspection_rules([], ["^philo\.contrib\.julian\.fields\.USZipCodeField"]) \ No newline at end of file diff --git a/contrib/julian/migrations/0001_initial.py b/contrib/julian/migrations/0001_initial.py new file mode 100644 index 0000000..d4864c6 --- /dev/null +++ b/contrib/julian/migrations/0001_initial.py @@ -0,0 +1,160 @@ +# encoding: 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 model 'Location' + db.create_table('julian_location', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('julian', ['Location']) + + # Adding model 'Event' + db.create_table('julian_event', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('start_date', self.gf('django.db.models.fields.DateField')()), + ('start_time', self.gf('django.db.models.fields.TimeField')(null=True, blank=True)), + ('end_date', self.gf('django.db.models.fields.DateField')()), + ('end_time', self.gf('django.db.models.fields.TimeField')(null=True, blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)), + ('location_content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), + ('location_pk', self.gf('django.db.models.fields.TextField')()), + ('description', self.gf('philo.models.fields.TemplateField')()), + ('parent_event', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['julian.Event'], null=True, blank=True)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + )) + db.send_create_signal('julian', ['Event']) + + # Adding M2M table for field tags on 'Event' + db.create_table('julian_event_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('event', models.ForeignKey(orm['julian.event'], null=False)), + ('tag', models.ForeignKey(orm['philo.tag'], null=False)) + )) + db.create_unique('julian_event_tags', ['event_id', 'tag_id']) + + # Adding model 'Calendar' + db.create_table('julian_calendar', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('uuid', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)), + )) + db.send_create_signal('julian', ['Calendar']) + + # Adding M2M table for field events on 'Calendar' + db.create_table('julian_calendar_events', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('calendar', models.ForeignKey(orm['julian.calendar'], null=False)), + ('event', models.ForeignKey(orm['julian.event'], null=False)) + )) + db.create_unique('julian_calendar_events', ['calendar_id', 'event_id']) + + + def backwards(self, orm): + + # Deleting model 'Location' + db.delete_table('julian_location') + + # Deleting model 'Event' + db.delete_table('julian_event') + + # Removing M2M table for field tags on 'Event' + db.delete_table('julian_event_tags') + + # Deleting model 'Calendar' + db.delete_table('julian_calendar') + + # Removing M2M table for field events on 'Calendar' + db.delete_table('julian_calendar_events') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + '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': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + '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': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + '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': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + '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'}) + }, + 'julian.calendar': { + 'Meta': {'object_name': 'Calendar'}, + 'events': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'calendars'", 'symmetrical': 'False', 'to': "orm['julian.Event']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'julian.event': { + 'Meta': {'object_name': 'Event'}, + 'description': ('philo.models.fields.TemplateField', [], {}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'end_time': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'location_pk': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'parent_event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['julian.Event']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'start_time': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.Tag']", 'null': 'True', 'blank': 'True'}) + }, + 'julian.location': { + 'Meta': {'object_name': 'Location'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'philo.attribute': { + 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'}, + 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}), + 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}), + 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'philo.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}) + } + } + + complete_apps = ['julian'] diff --git a/contrib/julian/migrations/__init__.py b/contrib/julian/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/contrib/julian/models.py b/contrib/julian/models.py index c9bd6da..29f5fd4 100644 --- a/contrib/julian/models.py +++ b/contrib/julian/models.py @@ -1,15 +1,12 @@ from django.conf import settings -from django.contrib.localflavor.us.models import USStateField -from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.generic import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError -from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator +from django.core.validators import RegexValidator from django.db import models -from philo.contrib.julian.fields import USZipCodeField -from philo.models.base import Tag, Entity, Titled +from philo.models.base import Tag, Entity from philo.models.fields import TemplateField -from philo.utils import ContentTypeSubclassLimiter -import datetime +from philo.utils import ContentTypeRegistryLimiter import re @@ -18,53 +15,25 @@ import re FPI_REGEX = re.compile(r"(|\+//|-//)[^/]+//[^/]+//[A-Z]{2}") -class Location(Entity): - name = models.CharField(max_length=150, blank=True) - description = models.TextField(blank=True) - - longitude = models.FloatField(blank=True, validators=[MinValueValidator(-180), MaxValueValidator(180)]) - latitude = models.FloatField(blank=True, validators=[MinValueValidator(-90), MaxValueValidator(90)]) - - events = GenericRelation('Event') - - def clean(self): - if not (self.name or self.description) or (self.longitude is None and self.latitude is None): - raise ValidationError("Either a name and description or a latitude and longitude must be defined.") - - class Meta: - abstract = True +location_content_type_limiter = ContentTypeRegistryLimiter() -_location_content_type_limiter = ContentTypeSubclassLimiter(Location) +def register_location_model(model): + location_content_type_limiter.register_class(model) -# TODO: Can we track when a building is open? Hmm... -class Building(Location): - """A building is a location with a street address.""" - address = models.CharField(max_length=255) - city = models.CharField(max_length=150) - - class Meta: - abstract = True - - -_building_content_type_limiter = ContentTypeSubclassLimiter(Building) +def unregister_location_model(model): + location_content_type_limiter.unregister_class(model) -class USBuilding(Building): - state = USStateField() - zipcode = USZipCodeField() +class Location(Entity): + name = models.CharField(max_length=255) - class Meta: - verbose_name = "Building (US)" - verbose_name_plural = "Buildings (US)" + def __unicode__(self): + return self.name -class Venue(Location): - """A venue is a location inside a building""" - building_content_type = models.ForeignKey(ContentType, limit_choices_to=_building_content_type_limiter) - building_pk = models.TextField() - building = GenericForeignKey('building_content_type', 'building_pk') +register_location_model(Location) class TimedModel(models.Model): @@ -76,12 +45,22 @@ class TimedModel(models.Model): def is_all_day(self): return self.start_time is None and self.end_time is None + def clean(self): + if bool(self.start_time) != bool(self.end_time): + raise ValidationError("A %s must have either a start time and an end time or neither.") + + if self.start_date > self.end_date or self.start_date == self.end_date and self.start_time > self.end_time: + raise ValidationError("A %s cannot end before it starts." % self.__class__.__name__) + class Meta: abstract = True -class Event(Entity, Titled, TimedModel): - location_content_type = models.ForeignKey(ContentType, limit_choices_to=_location_content_type_limiter) +class Event(Entity, TimedModel): + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255) + + location_content_type = models.ForeignKey(ContentType, limit_choices_to=location_content_type_limiter) location_pk = models.TextField() location = GenericForeignKey('location_content_type', 'location_pk') @@ -92,6 +71,8 @@ class Event(Entity, Titled, TimedModel): parent_event = models.ForeignKey('self', blank=True, null=True) owner = models.ForeignKey(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User')) + + # TODO: Add uid - use as pk? class Calendar(Entity):