Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Going with the Flow with Django Admin

Going with the Flow with Django Admin

Talk from DjangoCon Europe 2016

Marysia Lowas-Rzechonek

April 08, 2016
Tweet

Other Decks in Programming

Transcript

  1. Going with the Flow with Django Admin Django Girls Winter

    of Code - Kasia Siedlarek - Becky Smith - me - Ania Warzecha
  2. What’s the plan 1. Create a simple work flow in

    Django Admin by: a. Writing custom views b. Adding custom URLs c. Overriding default templates 2. Add a column to the list view with a value calculated on the database level.
  3. The admin has many hooks for customization, but beware of

    trying to use those hooks exclusively. If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views. [Django Docs]
  4. class Job(PublishFlowModel): OPEN = 'OPN' UNDER_REVIEW = 'URE' # ...

    STATUSES = ( (OPEN, 'Open'), (UNDER_REVIEW, 'Under review'), # ... ) title = models.CharField(max_length=255) company = models.CharField(max_length=255) description = models.TextField() # ... reviewer = models.ForeignKey(User, blank=True, null=True) review_status = models.CharField(choices=STATUSES, default=OPEN) expiration_date = models.DateField(blank=True, null=True) # ...
  5. class JobAdmin(admin.ModelAdmin): fieldsets = (('Job info', {'fields': ('title', 'company', 'website',

    'contact_email', ('cities', 'country'), 'state_province', 'description', 'remote_work', 'relocation') }),) readonly_fields = ('review_status', 'reviewer', 'published_date')
  6. class JobAdmin(admin.ModelAdmin): fieldsets = (('Job info', {'fields': ('title', 'company', 'website',

    'contact_email', ('cities', 'country'), 'state_province', 'description', 'remote_work', 'relocation') }),) readonly_fields = ('review_status', 'reviewer', 'published_date')
  7. Flow steps - model class Job(PublishFlowModel): # … def assign(self,

    user): assert self.review_status == self.OPEN self.reviewer = user self.review_status = self.UNDER_REVIEW self.save() def unassign(self): assert self.review_status == self.UNDER_REVIEW self.reviewer = None self.review_status = self.OPEN self.save() # …
  8. Flow steps - custom actions class JobAdmin(admin.ModelAdmin): # ... def

    assign_reviewer(self, request, id): post = get_object_or_404(self.model, id=id) post.assign(request.user) messages.add_message( request, messages.INFO, '{0} is now assigned.'.format(post.title) ) return redirect(reverse('admin:jobs_job_change', args=(id,)))
  9. Custom URLs class JobAdmin(admin.ModelAdmin): # ... def get_urls(self): urls =

    super(JobAdmin, self).get_urls() my_urls = [ url( r'^(?P<id>\d+)/assign/$', self.admin_site.admin_view(self.assign_reviewer), name='assign_job_reviewer' ), # ... ] return my_urls + urls
  10. {% extends "admin/change_form.html" %} {% load i18n %} {% block

    object-tools-items %} {{ block.super }} {% if original.review_status == original.OPEN %} <li><a href="{% url 'admin:assign_job_reviewer' original.id %}"> <i class="icon-eye-open icon-alpha75"></i>{% trans "Assign" %}</a> </li> {% endif %} {% if original.review_status == original.UNDER_REVIEW %} ... {% endif %} ... {% endblock %}
  11. {% extends "admin/change_form.html" %} {% load i18n %} {% block

    object-tools-items %} {{ block.super }} {% if original.review_status == original.OPEN %} <li><a href="{% url 'admin:assign_job_reviewer' original.id %}"> <i class="icon-eye-open icon-alpha75"></i>{% trans "Assign" %}</a> </li> {% endif %} {% if original.review_status == original.UNDER_REVIEW %} ... {% endif %} ... {% endblock %}
  12. {% extends "admin/change_form.html" %} {% load i18n %} {% block

    form_top %} {{ block.super }} {% if change %} {% if not original.not_expired %} <h3 class="text-error">{% trans "Expired" %}</h3> {% endif %} {% endif %} {% endblock %}
  13. {% extends "admin/change_form.html" %} {% load i18n %} {% block

    form_top %} {{ block.super }} {% if change %} {% if not original.not_expired %} <h3 class="text-error">{% trans "Expired" %}</h3> {% endif %} {% endif %} {% endblock %}
  14. class JobAdmin(admin.ModelAdmin): def not_expired(self, obj): if obj.expiration_date is None: return

    True return obj.expiration_date > timezone.now().date() not_expired.boolean = True
  15. Annotating queryset class Greater(CombinedExpression): def __init__(self, lhs, rhs): super(Greater, self).__init__(lhs,

    ">", rhs, output_field=BooleanField()) def get_queryset(self): not_expired = Greater(F('expiration_date'), Now()) return super(PublishFlowManager, self).get_queryset().annotate( not_expired= )
  16. Annotating queryset class Greater(CombinedExpression): def __init__(self, lhs, rhs): super(Greater, self).__init__(lhs,

    ">", rhs, output_field=BooleanField()) def get_queryset(self): not_expired = Greater(F('expiration_date'), Now()) return super(PublishFlowManager, self).get_queryset().annotate( not_expired=Coalesce(not_expired, Value(True)) )
  17. Annotating queryset class Greater(CombinedExpression): def __init__(self, lhs, rhs): super(Greater, self).__init__(lhs,

    ">", rhs, output_field=BooleanField()) class PublishFlowManager(models.Manager): def get_queryset(self): not_expired = Greater(F('expiration_date'), Now()) return super(PublishFlowManager, self).get_queryset().annotate( not_expired=Coalesce(not_expired, Value(True)) ) class Job(PublishFlowModel): # ... objects = PublishFlowManager()
  18. Custom CSS {% extends "admin/change_form.html" %} {% load i18n admin_static

    %} {% block extrastyle %} {{ block.super }} <link rel="stylesheet" type="text/css" href="{% static 'css/flow.css' %}" /> {% endblock extrastyle %} {% block object-tools-items %} {{ block.super }} <li class="flow-main"> <a href="{% url 'admin:assign_job_reviewer' original.id %}"> {% trans "Assign" %}</a></li> {% endblock %}
  19. Recap 1. Define the steps in Model. 2. Define custom

    actions/views in ModelAdmin. 3. Add custom URLs. 4. Extend templates. 5. ??? 6. PROFIT! ⭑ Annotate a queryset. ⭑ Add an extra column to the change list. ⭑ Make this column sortable.
  20. More... Django Girls repository https://github.com/DjangoGirls/djangogirls Ola Sitarska - Pushing the

    pony’s boundaries, DjangoCon Europe 2015 https://vimeo.com/channels/952478/134817269