Jul 232017
 

For a while there XML.com did­n’t handle tags on sub­mit­ted news items very well. If a tag was included that was in a dif­fer­ent case to an exist­ing tag, the pre­view and pub­lish would res­ult in a 500 serv­er error. For­tu­nately this was some­thing that was­n’t vis­ible to the out­side world, but annoy­ing nonetheless.

Wag­tail allows case-insens­it­ive tags, and I had already turned that on (it would be con­fus­ing to have searches for the tags “XSLT” and “xslt” return dif­fer­ent res­ults, for example). Art­icles and news items sub­mit­ted using the stand­ard inter­face behaved prop­erly, it was just the news items sub­mit­ted by people without logins on the sys­tem that didn’t.

It turns out that the prob­lem lay in the way I called the get_or_create() meth­od, which is used to look up the tags in the data­base and then cre­ate them if they don’t exist. In my code, that looked like this: 

tag, create = Tag.objects.get_or_create(name=tag_name)

By default, this is a case-sens­it­ive meth­od (as it should be, for the gen­er­al case). To make the look­up case-insens­it­ive, you use name__iexact instead of name. The next prob­lem I found was that no tags were being cre­ated if the tag did­n’t already exist in the data­base. To cre­ate the tag, if you’re using name__iexact instead of name for the tag look­up, you also need to give the get_or_create() meth­od a defaults para­met­er to use when cre­at­ing the tag. Now that line looks like this:

tag, create = Tag.objects.get_or_create(defaults={'name': tag_name},
                                        name__iexact=tag_name)

and it all works the way it’s meant to.

Feb 012017
 

I coded XML.com in Wag­tail, a CMS based on Django. It works well for my needs and I like Python as a pro­gram­ming lan­guage. One of the big reas­ons I like Wag­tail is that it includes a power­ful enough but not overly com­plic­ated work­flow with roles and a built-in mod­er­a­tion and pre­view system.

But, I wanted a sys­tem where people could sub­mit news items that would go into the mod­er­a­tion queue without need­ing to sign up for a login first. For­tu­nately, Wag­tail makes that pos­sible, and there’s a nice art­icle by Erin Mul­laney at Wag­tail: 2 Steps for Adding Pages Out­side of the CMS that details all the steps you need. It all worked nicely in more recent ver­sions of Wag­tail (thanks, Erin!) except for one part, the noti­fic­a­tion that the news item is in the mod­er­a­tion queue. That was­n’t a stop-ship item, so XML.com launched without those emails working.

I’ve now found the source of the prob­lem. It turns out that when you sub­mit a news item in this way, it does­n’t have a login iden­tity attached to it (obvi­ously, since there isn’t one). The send_notification func­tion that sends the email uses tem­plates, and these tem­plates use the login iden­tity of the author in the body of the email. Since that does­n’t exist, the whole func­tion fails. 

That means the solu­tion is easy. The affected tem­plates are wagtailadmin/notifications/submitted.txt and wagtailadmin/notifications/submitted.html, and Wag­tail lets you cus­tom­ize the admin tem­plates. I put my cus­tom­ized admin tem­plates into a utils applic­a­tion, which con­tains all my util­it­ies for the site. My utils/templates/wagtailadmin/notifications/submitted.txt file now has the content

{% extends 'wagtailadmin/notifications/submitted.txt' %}
{% load i18n %}

{% block content %}
{% blocktrans with page=revision.page|safe %}The page "{{ page }}" has been submitted for moderation.{% endblocktrans %}

{% trans "You can preview the page here:" %} {{ settings.BASE_URL }}{% url 'wagtailadmin_pages:preview_for_moderation' revision.id %}
{% trans "You can edit the page here:" %} {{ settings.BASE_URL }}{% url 'wagtailadmin_pages:edit' revision.page.id %}
{% endblock %}

Sim­il­ar changes are neces­sary for the wagtailadmin/notifications/submitted.html file if you want to send HTML emails instead.

May 102016
 

If you read the doc­u­ment­a­tion closely enough, of course all the inform­a­tion is there. Get­ting the order of oper­a­tions right, how­ever, can cause the odd issue.

Devel­op­ing Django apps means apply­ing migra­tions, and those don’t always do what’s expec­ted. In that case, you can roll back to the n‑1 migra­tion by using ./manage.py migrate [app_label] {n-1_migration_label}, then delete the nth migra­tion, then edit the models.py to try again.

To clean up the data­base from some third-party app you decide you don’t want after all, you use ./manage.py migrate [app_label] zero to get rid of the migra­tions from that app. You have to run this before delet­ing the app from your settings.py file.

Apr 222016
 

I dis­covered anoth­er issue while deploy­ing to Python­Any­where (maybe it’s applic­able to oth­er PAAS pro­viders as well).

There was an odd Impor­t­Er­ror when run­ning manage.py. In the spe­cif­ic case I had, it showed up when run­ning the tests with coverage: from Uni­path import Path Impor­t­Er­ror: No mod­ule named ‘Uni­path’. It turned out I had­n’t installed cov­er­age in the vir­tu­al envir­on­ment, which meant the sys­tem was using the default one. Installing cov­er­age in the vir­tu­al envir­on­ment as well fixed the problem.

Apr 212016
 

A check­list for mov­ing a Django-Wag­tail pro­ject to Python­Any­where. There is doc­u­ment­a­tion on the Python­Any­where site; mine includes things I forget.

Setup: devel­op­ment and test­ing on my laptop, sta­ging and pro­duc­tion on PythonAnywhere.

The help files are pretty good, but I need my own check­list. Right now I’m in the sta­ging mode, but at some stage I’ll be mov­ing to pro­duc­tion. No point fig­ur­ing out the same things twice!

  1. Devel­op on laptop in a vir­tualenv. Push com­mits reg­u­larly to bit­buck­et account. At some stage squash the migra­tions and clean those up. Four sets of set­tings: dev, test­ing, sta­ging, production.
  2. Set up account on Python­Any­where that allows the use of Post­gres (it’s an add-on to a cus­tom plan).
  3. Cre­ate vir­tualenv and set up sta­ging web app. Delete vir­tualenv when you real­ise you did­n’t use the right ver­sion of Python and the default is 2.7, not 3.5. Recre­ate the vir­tualenv with python 3.5.
  4. Clone the repos­it­ory (using the ssh-key­gen instruc­tions). Redir­ect the pub­lic key to a file so you can copy it without line-breaks get­ting in the way.
  5. pip install -r requirements/production.txt (includ­ing psycopg2, which I did­n’t need for development). 
  6. Cre­ate the Post­gres serv­er, user, and data­base Don’t for­get a strong pass­word for the user (own­er of the pro­ject database).
  7. Update the set­tings file with the data­base settings.
  8. Set the envir­on­ment vari­ables for the set­tings and the secret key (gen­er­at­or).
  9. Attempt to apply the migra­tions. This will show where you made mis­takes on all the pre­ced­ing steps.
  10. Fix the mis­takes. Reload the web app to see if any­thing shows up. 
  11. Set up the stat­ic file serv­er. Check the stat­ic files are being served correctly.
  12. Cre­ate the Django super­user and log in.

The next step is data, of course.

/* ]]> */