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

Going with the Flow with Django Admin

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Going with the Flow with Django Admin

Talk from DjangoCon Europe 2016

Avatar for Marysia Lowas-Rzechonek

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