1. rmoch
  2. django-email-notification

Commits

rmoch  committed 6331cb2

Full django admin integration

  • Participants
  • Parent commits 9135eec
  • Branches default

Comments (0)

Files changed (8)

File notification/__init__.py

View file
 # -*- coding: utf-8 -*-
 
-__version__ = '1.1.0'
+__version__ = '1.2.0'
 __author__ = 'Richard Moch <richard@rootsaka.com>'

File notification/admin.py

View file
 # -*- coding: utf-8 -*-
 
+from django import forms
 from django.conf import settings
 from django.contrib import admin
+from django.contrib.contenttypes.models import ContentType
+
 from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext_lazy as _
 
-from .models import SimpleRegistration, Notification, RecipientGroup, Hit
+from .models import SimpleRegistration, Notification, RecipientGroup, Hit, GetAttrMixin, Item
 
 
 def registered(obj):
     list_display = ['registration_date', 'email', registered]
 admin.site.register(SimpleRegistration, SimpleRegistrationAdmin)
 
-#admin.site.register(EmailLogEntry)
 
 admin.site.register(RecipientGroup)
 
 admin.site.register(Hit, HitAdmin)
 
 
-# TODO: We'd like to use forms.NotificationForm directly in admin, but dynamic fields won't show up.
-# http://stackoverflow.com/questions/5718838/django-adding-new-form-fields-dynamically-in-admin
-# This probleme is unsatisfyingly solved by overriding host project admin routes to notification model (add and edit)
+class DynFieldMixin(GetAttrMixin):
+
+    def init_dynamic_fields(self):
+        """Initialize form."""
+        if self.instance.id:
+            for item in self.instance.items.all():
+                for object_type in settings.NOTIFICATION_ASSOCIATED_OBJECTS:
+                    if self._get_class(object_type['class']) == item.content_type.model_class():
+                        if object_type['name'] not in self.initial:
+                            self.initial[object_type['name']] = []
+                        self.initial[object_type['name']].append(item.object_id)
+
+    def dynamic_fields(self):
+        """Returns form fields for linkable objects."""
+        fields = {}
+        for object_type in settings.NOTIFICATION_ASSOCIATED_OBJECTS:
+            C = self._get_class(object_type['class'])
+            if object_type['manager']:
+                objects_manager = getattr(C.objects, object_type['manager'])
+            else:
+                objects_manager = getattr(C.objects, 'all')
+            choices = [(o.id, self._get_title_attr(o, object_type['title_attr']))
+                    for o in objects_manager().order_by(object_type['order_by'])]
+            fields[object_type['name']] = forms.MultipleChoiceField(choices=choices,
+                    label=object_type['description'],
+                    required=False)
+        return fields
+
+    def get_all_fields(self):
+        all_fields = self.fields.copy()
+        all_fields.update(self.dynamic_fields())
+        return all_fields
+
+    def save_dynamic_fields(self, commit=True):
+        """Save linked objects."""
+
+        self.instance.items.clear()
+        for object_type in settings.NOTIFICATION_ASSOCIATED_OBJECTS:
+            C = self._get_class(object_type['class'])
+            ct = ContentType.objects.get_for_model(C)
+            if len(self.cleaned_data[object_type['name']]):
+                for id in self.cleaned_data[object_type['name']]:
+                    item = Item.objects.create(content_type=ct,
+                            object_id=id,
+                    )
+                    self.instance.items.add(item)
+        self.instance.save()
+        return self.instance
+
+
+class NotificationFormAdmin(forms.ModelForm, DynFieldMixin):
+
+    def __init__(self, *args, **kwargs):
+        super(NotificationFormAdmin, self).__init__(*args, **kwargs)
+        self.init_dynamic_fields()
+
+    def save(self, *args, **kwargs):
+        super(NotificationFormAdmin, self).save(*args, **kwargs)
+        self.save_dynamic_fields()
+        return self.instance
+
+    class Meta:
+        model = Notification
+        fields = ('subject', 'from_name', 'from_email', 'body', 'recipient_groups', 'registered_recipients', 'recipients')
+
+
 def preview_link(obj):
     return '<a href="%s">Preview</a>' % reverse('view_notification', args=[obj.md5])
 preview_link.allow_tags = True
     list_display = ['subject', 'body', 'creation_date', 'from_name', 'from_email', preview_link]
     ordering = ['-creation_date']
     search_fields = ['subject', 'body']
+
+    def get_form(self, request, obj=None, **kwargs):
+        """Inherit a new pure ModelForm from NotificationFormAdmin including dynamicaly created fields."""
+        form = NotificationFormAdmin()
+        new_form = type('NotificationFormAdmin', (NotificationFormAdmin, ), form.get_all_fields())
+        return new_form
+
+    def save_model(self, request, obj, form, change):
+        super(NotificationAdmin, self).save_model(request, obj, form, change)
+        if "save-and-send" in request.POST:
+            obj.send_emails()
+
 admin.site.register(Notification, NotificationAdmin)

File notification/forms.py

View file
 # -*- coding: utf-8 -*-
 
-from django.conf import settings
-from django.contrib.contenttypes.models import ContentType
 from django import forms
 
-from models import Notification, SimpleRegistration, Item, GetAttrMixin
+from .models import SimpleRegistration
 
+from .admin import NotificationFormAdmin
 
-class NotificationForm(forms.ModelForm, GetAttrMixin):
 
-    def _get_title_attr(self, instance, title_attr):
-        """title_attr attribute of settings may be a callable."""
-        attr = getattr(instance, title_attr)
-        return attr() if callable(attr) else attr
-
+class NotificationForm(NotificationFormAdmin):
     def __init__(self, *args, **kwargs):
         super(NotificationForm, self).__init__(*args, **kwargs)
-
-        # create form fields for linkable objects
-        for object_type in settings.NOTIFICATION_ASSOCIATED_OBJECTS:
-            C = self._get_class(object_type['class'])
-            if object_type['manager']:
-                objects_manager = getattr(C.objects, object_type['manager'])
-            else:
-                objects_manager = getattr(C.objects, 'all')
-            choices = [(o.id, self._get_title_attr(o, object_type['title_attr']))
-                    for o in objects_manager().order_by(object_type['order_by'])]
-            self.fields[object_type['name']] = forms.MultipleChoiceField(choices=choices,
-                    label=object_type['description'],
-                    required=False)
-        # select linked objects if form have model instance
-        if self.instance.id:
-            for item in self.instance.items.all():
-                for object_type in settings.NOTIFICATION_ASSOCIATED_OBJECTS:
-                    if self._get_class(object_type['class']) == item.content_type.model_class():
-                        if object_type['name'] not in self.initial:
-                            self.initial[object_type['name']] = []
-                        self.initial[object_type['name']].append(item.object_id)
-
-    class Meta:
-        model = Notification
-        fields = ('subject', 'from_name', 'from_email', 'body', 'recipient_groups', 'registered_recipients', 'recipients')
-
-
-    def save(self, commit=True):
-        notification = super(NotificationForm, self).save(commit=commit)
-        notification.items.clear()
-        # save linked objects
-        for object_type in settings.NOTIFICATION_ASSOCIATED_OBJECTS:
-            C = self._get_class(object_type['class'])
-            ct = ContentType.objects.get_for_model(C)
-            if len(self.cleaned_data[object_type['name']]):
-                for id in self.cleaned_data[object_type['name']]:
-                    item = Item.objects.create(content_type=ct,
-                            object_id=id,
-                    )
-                    notification.items.add(item)
-        notification.save()
-        return notification
+        self.fields = self.get_all_fields()
 
 
 class SimpleRegistrationForm(forms.ModelForm):
 
     class Meta:
         model = SimpleRegistration
-        fields = ['email',]
+        fields = ['email']

File notification/models.py

View file
     def _get_class(self, path):
         return getattr(__import__(path.rsplit('.', 1)[0], fromlist=['']), path.rsplit('.', 1)[1])
 
+    def _get_title_attr(self, instance, title_attr):
+        """title_attr attribute of settings may be a callable."""
+        attr = getattr(instance, title_attr)
+        return attr() if callable(attr) else attr
+
 
 class ModelWithMD5(models.Model):
     """Generate MD5 at creation."""

File notification/templates/admin/notification/notification/change_form.html

View file
+{% extends "admin/change_form.html" %}
+{% load i18n %}
+{% block after_related_objects %}
+<div class="submit-row">
+    <input type="submit" name="save-and-send" value="{% trans "Save & send" %}" style="color: white; font-weight: bold; background: none; background-color: red !important;">
+</div>
+{% endblock %}

File notification/templates/notification/emails/tracked_link.html

View file
     <a href="http://{{ site }}{% url 'track_link' notification.md5 instance.item.pk recipient.md5 %}">{{ instance.get_title }}</a>
 {% else %}
     {% comment %}
-        If we preview the notification or send it to a unregistred recipient, we can't track links
+        If we preview the notification or send it to a unregistred recipient, we can't or don't want to track links
     {% endcomment %}
     {% include 'notification/emails/untracked_link.html' %}
 {% endif %}

File notification/templates/notification/notification_list.html

View file
-{% extends "core/base.html" %}
+{% extends "base.html" %}
 {% load url from future %}
 {% load i18n %}
 {% block page_title %}{% endblock %}

File notification/views.py

View file
 from django.contrib import messages
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import Http404
-#from django.core.urlresolvers import reverse
 from django.shortcuts import redirect, render, get_object_or_404
 from django.utils.translation import ugettext as _
 from django.views.decorators.http import require_POST