event_rsvp.models: 132 total statements, 84.8% covered

Generated: Tue 2013-04-16 10:12 CEST

Source file: /home/tobi/Projects/event-rsvp/src/event_rsvp/models.py

Stats: 95 executed, 17 missed, 20 excluded, 230 ignored

  1. """Models for the ``event_rsvp`` application."""
  2. from django import forms
  3. from django.core import exceptions
  4. from django.core.urlresolvers import reverse
  5. from django.db import models
  6. from django.template.defaultfilters import date, slugify
  7. from django.utils import timezone
  8. from django.utils.text import capfirst
  9. from django.utils.translation import ugettext
  10. from django.utils.translation import ugettext_lazy as _
  11. class MultiSelectFormField(forms.MultipleChoiceField):
  12. widget = forms.CheckboxSelectMultiple
  13. def clean(self, value):
  14. if not value and self.required:
  15. raise forms.ValidationError(self.error_messages['required'])
  16. return value
  17. class MultiSelectField(models.Field):
  18. __metaclass__ = models.SubfieldBase
  19. def get_internal_type(self):
  20. return "CharField"
  21. def get_choices_default(self):
  22. return self.get_choices(include_blank=False)
  23. def formfield(self, **kwargs):
  24. # don't call super, as that overrides default widget if it has choices
  25. defaults = {'required': not self.blank,
  26. 'label': capfirst(self.verbose_name),
  27. 'help_text': self.help_text, 'choices': self.choices}
  28. if self.has_default():
  29. defaults['initial'] = self.get_default()
  30. defaults.update(kwargs)
  31. return MultiSelectFormField(**defaults)
  32. def get_prep_value(self, value):
  33. return value
  34. def get_db_prep_value(self, value, connection=None, prepared=False):
  35. if isinstance(value, basestring):
  36. return value
  37. elif isinstance(value, list):
  38. return ",".join(value)
  39. def to_python(self, value):
  40. if value is not None:
  41. return value if isinstance(value, list) else value.split(',')
  42. return ''
  43. def contribute_to_class(self, cls, name):
  44. super(MultiSelectField, self).contribute_to_class(cls, name)
  45. if self.choices:
  46. func = lambda self, fieldname = name, choicedict = dict(
  47. self.choices): ",".join([choicedict.get(
  48. value, value) for value in getattr(self, fieldname)])
  49. setattr(cls, 'get_%s_display' % self.name, func)
  50. def validate(self, value, model_instance):
  51. arr_choices = self.get_choices_selected(self.get_choices_default())
  52. for opt_select in value:
  53. if opt_select not in arr_choices:
  54. raise exceptions.ValidationError(
  55. self.error_messages['invalid_choice'] % value)
  56. return
  57. def get_choices_selected(self, arr_choices=''):
  58. if not arr_choices:
  59. return False
  60. list = []
  61. for choice_selected in arr_choices:
  62. list.append(choice_selected[0])
  63. return list
  64. def value_to_string(self, obj):
  65. value = self._get_val_from_obj(obj)
  66. return self.get_db_prep_value(value)
  67. from south.modelsinspector import add_introspection_rules
  68. add_introspection_rules([], ["^event_rsvp\.models\.MultiSelectField"])
  69. class Event(models.Model):
  70. """
  71. Model to create event templates for recurring events etc.
  72. :created_by: User, who owns this template.
  73. :creation_date: Date of the template creation.
  74. :title: Title of the template.
  75. :description: Description of the template.
  76. :start: Starting date of the event.
  77. :end: Ending date of the event.
  78. :venue: The event location.
  79. :street: Street of the event location.
  80. :city: City of the event location.
  81. :zip: ZIP code of the event location.
  82. :country: Country of the event location.
  83. :contact_person: Name of a person to contact.
  84. :contact_email: Email of a person to contact.
  85. :contact_phone: Phone of a person to contact.
  86. :available_seats: Amount of seats available for this event.
  87. :hide_available_seats: Checkfield to hide the information about available
  88. seats in the templates.
  89. :max_seats_per_guest: Maximum amount of seats per guest.
  90. :allow_anonymous_rsvp: Checkbox to allow anonymous responses.
  91. :required_fields: Checkbox to select required guest fields.
  92. :template_name: Name can be set, if this event should be reusable.
  93. :is_published: Checkbox to publish/unpublish an event.
  94. """
  95. created_by = models.ForeignKey(
  96. 'auth.User',
  97. verbose_name=_('Created by'),
  98. )
  99. creation_date = models.DateTimeField(
  100. auto_now_add=True,
  101. verbose_name=_('Creation date'),
  102. )
  103. title = models.CharField(
  104. max_length=256,
  105. verbose_name=_('Title'),
  106. help_text=_('The title will also be used for the event URL.'),
  107. )
  108. slug = models.SlugField(
  109. max_length=256,
  110. verbose_name=_('Slug'),
  111. )
  112. description = models.TextField(
  113. max_length=1000,
  114. verbose_name=_('Description'),
  115. blank=True, null=True,
  116. )
  117. start = models.DateTimeField(
  118. default=timezone.now(),
  119. verbose_name=_('Start date'),
  120. )
  121. end = models.DateTimeField(
  122. default=timezone.now() + timezone.timedelta(days=1),
  123. verbose_name=_('End date'),
  124. )
  125. venue = models.CharField(
  126. max_length=100,
  127. verbose_name=_('Venue'),
  128. )
  129. street = models.CharField(
  130. max_length=100,
  131. verbose_name=_('Street'),
  132. blank=True,
  133. )
  134. city = models.CharField(
  135. max_length=100,
  136. verbose_name=_('City'),
  137. blank=True,
  138. )
  139. zip = models.CharField(
  140. max_length=100,
  141. verbose_name=_('ZIP code'),
  142. blank=True,
  143. )
  144. country = models.CharField(
  145. max_length=100,
  146. verbose_name=_('Country'),
  147. blank=True,
  148. )
  149. contact_person = models.CharField(
  150. max_length=100,
  151. verbose_name=_('Contact name'),
  152. blank=True,
  153. )
  154. contact_email = models.EmailField(
  155. verbose_name=_('Contact email'),
  156. blank=True,
  157. )
  158. contact_phone = models.CharField(
  159. max_length=100,
  160. verbose_name=_('Contact phone'),
  161. blank=True,
  162. )
  163. available_seats = models.PositiveIntegerField(
  164. verbose_name=_('Available seats'),
  165. blank=True, null=True,
  166. )
  167. hide_available_seats = models.BooleanField(
  168. default=False,
  169. verbose_name=_('Hide available seat information'),
  170. )
  171. allow_anonymous_rsvp = models.BooleanField(
  172. default=False,
  173. verbose_name=_('Allow anonymous RSVP'),
  174. help_text=_('Even anonymous users can rsvp, without adding any info.'),
  175. )
  176. required_fields = MultiSelectField(
  177. verbose_name=_('Required fields'),
  178. max_length=250, blank=True,
  179. choices=(
  180. ('name', _('Name')),
  181. ('email', _('Email')),
  182. ('phone', _('Phone')),
  183. ),
  184. )
  185. max_seats_per_guest = models.PositiveIntegerField(
  186. blank=True, null=True,
  187. verbose_name=_('Maximum amount of seats per guest'),
  188. )
  189. template_name = models.CharField(
  190. max_length=100,
  191. verbose_name=_('Save as template'),
  192. blank=True,
  193. help_text=_('Save this event as a template to re-use it later.'),
  194. )
  195. is_published = models.BooleanField(
  196. verbose_name=_('is published'),
  197. default=False,
  198. )
  199. def __unicode__(self):
  200. if self.template_name:
  201. return '{0} ({1})'.format(self.template_name, ugettext('Template'))
  202. return '{0} ({1})'.format(self.title, date(self.start))
  203. def save(self, *args, **kwargs):
  204. self.slug = slugify(self.title)
  205. suspects = Event.objects.filter(slug=self.slug)
  206. if suspects.count() > 0 and suspects[0] != self:
  207. while Event.objects.filter(slug=self.slug).count() > 0:
  208. try:
  209. number = int(self.slug[-1])
  210. except ValueError:
  211. self.slug = self.slug + '0'
  212. else:
  213. self.slug = self.slug[:-1] + str(number + 1)
  214. super(Event, self).save(*args, **kwargs)
  215. def get_absolute_url(self, url='rsvp_event_detail'):
  216. return reverse(url, kwargs={
  217. 'slug': self.slug,
  218. 'year': '{0:04d}'.format(self.start.year),
  219. 'month': '{0:02d}'.format(self.start.month),
  220. 'day': '{0:02d}'.format(self.start.day),
  221. })
  222. def get_update_url(self):
  223. return self.get_absolute_url(url='rsvp_event_update')
  224. def get_delete_url(self):
  225. return self.get_absolute_url(url='rsvp_event_delete')
  226. def get_template_url(self):
  227. return reverse('rsvp_event_create_from_template', kwargs={
  228. 'pk': self.pk})
  229. def get_free_seats(self):
  230. reserved = self.guests.all().aggregate(models.Sum('number_of_seats'))
  231. if self.available_seats:
  232. return self.available_seats - int(reserved.get(
  233. 'number_of_seats__sum') or 0)
  234. return _('Unlimited seats available.')
  235. def is_bookable(self):
  236. if self.start < timezone.now():
  237. return False
  238. return True
  239. class Guest(models.Model):
  240. """
  241. Model to create event templates for recurring events etc.
  242. :event: Event to visit.
  243. :user: User model of the guest.
  244. :name: Name of the guest.
  245. :email: Email of the guest.
  246. :phone: Phone number of the guest.
  247. :number_of_seats: Amount of seats to book.
  248. :creation_date: Date of the guest model creation.
  249. :is_attending: If the user is attending or not. Default: True
  250. :message: A response from a potential attendee.
  251. """
  252. event = models.ForeignKey(
  253. 'event_rsvp.Event',
  254. verbose_name=_('Event'),
  255. related_name='guests',
  256. )
  257. user = models.ForeignKey(
  258. 'auth.User',
  259. verbose_name=_('User'),
  260. blank=True, null=True,
  261. )
  262. name = models.CharField(
  263. max_length=50,
  264. verbose_name=_('Name'),
  265. blank=True,
  266. )
  267. email = models.EmailField(
  268. verbose_name=_('Email'),
  269. blank=True,
  270. )
  271. phone = models.CharField(
  272. max_length=50,
  273. verbose_name=_('Phone'),
  274. blank=True,
  275. )
  276. number_of_seats = models.PositiveIntegerField(
  277. verbose_name=_('Number of seats'),
  278. blank=True, null=True,
  279. )
  280. creation_date = models.DateTimeField(
  281. auto_now_add=True,
  282. verbose_name=_('Creation date'),
  283. )
  284. is_attending = models.BooleanField(
  285. verbose_name=_('Attending'),
  286. default=True,
  287. )
  288. message = models.TextField(
  289. verbose_name=_('Message'),
  290. max_length=4000,
  291. blank=True,
  292. )
  293. def __unicode__(self):
  294. if self.user:
  295. return '{0} - {1}'.format(
  296. self.user.get_full_name() or self.user.email, self.event)
  297. elif self.name or self.email:
  298. return '{0} - {1}'.format(self.name or self.email, self.event)
  299. return '{0} - {1}'.format(ugettext('anonymous'), self.event)