1 from __future__ import unicode_literals
3 from django.contrib.sites.models import Site
4 from django.core.exceptions import ValidationError
5 from django.core.urlresolvers import reverse
6 from django.db import models
7 from django.db.models import Q
8 from django.utils.encoding import python_2_unicode_compatible
9 from django.utils.translation import ugettext, ugettext_lazy as _
10 from future.builtins import str
12 from forms_builder.forms import fields
13 from forms_builder.forms import settings
14 from forms_builder.forms.utils import now, slugify, unique_slug
20 (STATUS_DRAFT, _("Draft")),
21 (STATUS_PUBLISHED, _("Published")),
25 class FormManager(models.Manager):
27 Only show published forms for non-staff users.
29 def published(self, for_user=None):
30 if for_user is not None and for_user.is_staff:
33 Q(publish_date__lte=now()) | Q(publish_date__isnull=True),
34 Q(expiry_date__gte=now()) | Q(expiry_date__isnull=True),
35 Q(status=STATUS_PUBLISHED),
37 if settings.USE_SITES:
38 filters.append(Q(sites=Site.objects.get_current()))
39 return self.filter(*filters)
42 ######################################################################
44 # Each of the models are implemented as abstract to allow for #
45 # subclassing. Default concrete implementations are then defined #
46 # at the end of this module. #
48 ######################################################################
50 @python_2_unicode_compatible
51 class AbstractForm(models.Model):
56 sites = models.ManyToManyField(Site,
57 default=[settings.SITE_ID], related_name="%(app_label)s_%(class)s_forms")
58 title = models.CharField(_("Title"), max_length=50)
59 slug = models.SlugField(_("Slug"), editable=settings.EDITABLE_SLUGS,
60 max_length=100, unique=True)
61 intro = models.TextField(_("Intro"), blank=True)
62 button_text = models.CharField(_("Button text"), max_length=50,
64 response = models.TextField(_("Response"), blank=True)
65 redirect_url = models.CharField(_("Redirect url"), max_length=200,
66 null=True, blank=True,
67 help_text=_("An alternate URL to redirect to after form submission"))
68 status = models.IntegerField(_("Status"), choices=STATUS_CHOICES,
69 default=STATUS_PUBLISHED)
70 publish_date = models.DateTimeField(_("Published from"),
71 help_text=_("With published selected, won't be shown until this time"),
72 blank=True, null=True)
73 expiry_date = models.DateTimeField(_("Expires on"),
74 help_text=_("With published selected, won't be shown after this time"),
75 blank=True, null=True)
76 login_required = models.BooleanField(_("Login required"), default=False,
77 help_text=_("If checked, only logged in users can view the form"))
78 send_email = models.BooleanField(_("Send email"), default=True, help_text=
79 _("If checked, the person entering the form will be sent an email"))
80 email_from = models.EmailField(_("From address"), blank=True,
81 help_text=_("The address the email will be sent from"))
82 email_copies = models.CharField(_("Send copies to"), blank=True,
83 help_text=_("One or more email addresses, separated by commas"),
85 email_subject = models.CharField(_("Subject"), max_length=200, blank=True)
86 email_message = models.TextField(_("Message"), blank=True)
88 objects = FormManager()
91 verbose_name = _("Form")
92 verbose_name_plural = _("Forms")
96 return str(self.title)
98 def save(self, *args, **kwargs):
100 Create a unique slug from title - append an index and increment if it
105 self.slug = unique_slug(self.__class__.objects, "slug", slug)
106 super(AbstractForm, self).save(*args, **kwargs)
108 def published(self, for_user=None):
110 Mimics the queryset logic in ``FormManager.published``, so we
111 can check a form is published when it wasn't loaded via the
112 queryset's ``published`` method, and is passed to the
113 ``render_built_form`` template tag.
115 if for_user is not None and for_user.is_staff:
117 status = self.status == STATUS_PUBLISHED
118 publish_date = self.publish_date is None or self.publish_date <= now()
119 expiry_date = self.expiry_date is None or self.expiry_date >= now()
120 authenticated = for_user is not None and for_user.is_authenticated()
121 login_required = (not self.login_required or authenticated)
122 return status and publish_date and expiry_date and login_required
124 def total_entries(self):
126 Called by the admin list view where the queryset is annotated
127 with the number of entries.
129 return self.total_entries
130 total_entries.admin_order_field = "total_entries"
133 def get_absolute_url(self):
134 return ("form_detail", (), {"slug": self.slug})
136 def admin_links(self):
137 kw = {"args": (self.id,)}
139 (_("View form on site"), self.get_absolute_url()),
140 (_("Filter entries"), reverse("admin:form_entries", **kw)),
141 (_("View all entries"), reverse("admin:form_entries_show", **kw)),
142 (_("Export all entries"), reverse("admin:form_entries_export", **kw)),
144 for i, (text, url) in enumerate(links):
145 links[i] = "<a href='%s'>%s</a>" % (url, ugettext(text))
146 return "<br>".join(links)
147 admin_links.allow_tags = True
148 admin_links.short_description = ""
151 class FieldManager(models.Manager):
153 Only show visible fields when displaying actual form..
156 return self.filter(visible=True)
159 @python_2_unicode_compatible
160 class AbstractField(models.Model):
162 A field for a user-built form.
165 label = models.CharField(_("Label"), max_length=settings.LABEL_MAX_LENGTH)
166 slug = models.SlugField(_('Slug'), max_length=100, blank=True,
168 field_type = models.IntegerField(_("Type"), choices=fields.NAMES)
169 required = models.BooleanField(_("Required"), default=True)
170 visible = models.BooleanField(_("Visible"), default=True)
171 choices = models.CharField(_("Choices"), max_length=settings.CHOICES_MAX_LENGTH, blank=True,
172 help_text="Comma separated options where applicable. If an option "
173 "itself contains commas, surround the option starting with the %s"
174 "character and ending with the %s character." %
175 (settings.CHOICES_QUOTE, settings.CHOICES_UNQUOTE))
176 default = models.CharField(_("Default value"), blank=True,
177 max_length=settings.FIELD_MAX_LENGTH)
178 placeholder_text = models.CharField(_("Placeholder Text"), null=True,
179 blank=True, max_length=100, editable=settings.USE_HTML5)
180 help_text = models.CharField(_("Help text"), blank=True, max_length=settings.HELPTEXT_MAX_LENGTH)
182 objects = FieldManager()
185 verbose_name = _("Field")
186 verbose_name_plural = _("Fields")
190 return str(self.label)
192 def get_choices(self):
194 Parse a comma separated choice string into a list of choices taking
195 into account quoted choices using the ``settings.CHOICES_QUOTE`` and
196 ``settings.CHOICES_UNQUOTE`` settings.
200 for char in self.choices:
201 if not quoted and char == settings.CHOICES_QUOTE:
203 elif quoted and char == settings.CHOICES_UNQUOTE:
205 elif char == "," and not quoted:
206 choice = choice.strip()
212 choice = choice.strip()
216 def save(self, *args, **kwargs):
218 slug = slugify(self).replace('-', '_')
219 self.slug = unique_slug(self.form.fields, "slug", slug)
220 return super(AbstractField, self).save(*args, **kwargs)
222 def is_a(self, *args):
224 Helper that returns True if the field's type is given in any arg.
226 return self.field_type in args
229 class AbstractFormEntry(models.Model):
231 An entry submitted via a user-built form.
234 entry_time = models.DateTimeField(_("Date/time"))
237 verbose_name = _("Form entry")
238 verbose_name_plural = _("Form entries")
242 class AbstractFieldEntry(models.Model):
244 A single field value for a form entry submitted via a user-built form.
247 field_id = models.IntegerField()
248 value = models.CharField(max_length=settings.FIELD_MAX_LENGTH,
252 verbose_name = _("Form field entry")
253 verbose_name_plural = _("Form field entries")
257 ###################################################
259 # Default concrete implementations are below. #
261 ###################################################
263 class FormEntry(AbstractFormEntry):
264 form = models.ForeignKey("Form", related_name="entries")
267 class FieldEntry(AbstractFieldEntry):
268 entry = models.ForeignKey("FormEntry", related_name="fields")
271 class Form(AbstractForm):
275 class Field(AbstractField):
277 Implements automated field ordering.
280 form = models.ForeignKey("Form", related_name="fields")
281 order = models.IntegerField(_("Order"), null=True, blank=True)
283 class Meta(AbstractField.Meta):
284 ordering = ("order",)
286 def save(self, *args, **kwargs):
287 if self.order is None:
288 self.order = self.form.fields.count()
289 super(Field, self).save(*args, **kwargs)
291 def delete(self, *args, **kwargs):
292 fields_after = self.form.fields.filter(order__gte=self.order)
293 fields_after.update(order=models.F("order") - 1)
294 super(Field, self).delete(*args, **kwargs)