Including an extra button in a crispy form

I’m a big fan of using crispy forms with Django. In fact, since doing the Django girls tutorial, I don’t think I’ve made a form manually.

And, until now, I haven’t been super critical of aesthetics. Now, however, I’m updating the look of the worksheet generator and moved the login to a modal. Doing that, I thought… “Hmm, wouldn’t it be nice if the ‘reset password’ button was in a button group with the login button?

It turned out to be a bit of a challenge for me, and I couldn’t find anything on it specifically, so here’s what I did:

loginModal
The goal — Doesn’t that look nice?
oldLogin
What I had before. Too much screen real estate.

Functionally, it’s not a big deal, except that I’m using button groups more and more in the site in general, and why not use one here?

However, the old way I was rendering the form wouldn’t work. The crispy FormHelper object rendered the form so completely that I couldn’t get at the login button, much less wrap it together in in a button group.

Of course it was possible, but the solution turned out being telling crispy forms to let me create my own form tag:

# forms.py

class LoginForm(forms.Form):
    username = forms.CharField(label='Username:', max_length=100)
    password = forms.CharField(label='Password:', widget=forms.PasswordInput(), max_length=100)

    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False

The self.helper.form_tag = False bit turns off the form field.

Then, in the template rendering the form, I had to make some changes. To be honest, I should say that I took the easy way by having crispy forms render the form tag, and then copied that.

template
WordPress didn’t want to display this, so this seemed the easiest thing to do.

Initially, I did this without adding type=”button” to the second button in the group and, for reasons I don’t understand, both buttons functioned at a submit button. Obviously, that wasn’t ideal, and adding the extra type=”button” made it work.

It’s worth pointing out that I never (or, not yet) removed the old …/login.html page that rendered the form before. Nothing links to it, so I doubt anyone will ever hit it. But, if they did, the form would be broken because I didn’t update that template to include a form tag.

Advertisements

Mailchimp’s Subscriber Hash

I learned something today. In sitting down to finish up my mailchimp functions, I wanted to create a mailchimp_remove_teacher_from_list(listname) function and a mailchimp_blanket_unsubscibe_teacher() function. I thought that, after my previous success, it would go quickly.

I was wrong.

Over here, in the docs, there’s reference to how to delete list members, which is what I basically want to do:

client.lists.members.delete(list_id=”, subscriber_hash=”)

I knew what a list_id was, but wasn’t sure what the subscriber_hash was. After a bit of Googleing (I thought I’d have to ask mailchimp for the hash) I found this page and was able to piece it together:

In previous versions of the API, we exposed internal database IDs eid and leid for emails and list/email combinations. In API 3.0, we no longer use or expose either of these IDs. Instead, we identify your subscribers by the MD5 hash of the lowercase version of their email address so you can easily predict the API URL of a subscriber’s data.

For example, to get the MD5 hash of the email address Urist.McVankab@freddiesjokes.com, first convert the address to its lowercase version: urist.mcvankab@freddiesjokes.com. The MD5 hash of urist.mcvankab@freddiesjokes.com is 62eeb292278cc15f5817cb78f7790b08.

In short, the subscriber_hash argument is something you produce at your end and use to look a subscriber up. There’s something elegant about it — you have no business accessing the subscriber if you don’t know the email address, already — but I needed some time to get it done.

Here’s how I did it:

import hashlib # A standard library that does hashes

# The following is in .lower() case because mailchimp forms
# hashes from lowercase strings.
# The .encode() method tagged on the end encodes it as a byte literal
email = request.user.email.lower().encode(encoding='utf-8)

# This uses the hashlib library to make the hash. The .hexdigest()
# seems to be about equivalent to str() [forgive me internet!]
hash = hashlib.md5(email).hexdigest()

client.lists.members.delete(list_id=list_id, subscriber_hash=hash)

This is not a complete solution. It basically tags onto my last post. At some point, I want to compend everything I do into something more universally applicable. When I do, I have no plans to keep it secret.

Adding Mailchimp actions to a Django model

As I get ready to try a second time to attract users, I decided to use MailChimp mailing lists to keep in touch with these users, as well as setting up some ‘tutorial’ emails at regular intervals. (I’m imagining something like “You’ve been using dynamic-efl.com for a week now, did you know you can do this to get more out of it?”)

To do this, I’d have to have code in Django that automatically signed members up. That means that this is my first attempt to work with an API, even by way of a library.

I decided to go with the mailchimp3 library and installed it in my virtualenv according to the instructions and set up some variables in my settings.py file.

# Mailchimp stuff
# Authentication
MAILCHIMP_LOGIN = 'login'
MAILCHIMP_KEY = '===>SECRET<==='

# The lists
MAILCHIMP_LISTS = {'welcome': 'listID',
                   'germany': 'listID',
                   'beta-testers': 'listID'}<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

The idea here is that I’ll be able to iterate over the keys in the lists, if I ever need to check that the user is in all the lists (for example, removing a user should include removing him from all lists).

To that end, I added a function at the beginning of my models.py file. (This probably isn’t good form).

def mailchimp_get():
    """ Returns a logged-in mailchimp client """
    return MailChimp(settings.MAILCHIMP_LOGIN, settings.MAILCHIMP_KEY)<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

It seemed like the easiest way to only write the login once.

Finally, I had to create a few methods in the Model in question (in my case, this is my teacher model, as my users are called ‘Teachers’ in this bit.

def mailchimp_add_teacher_to_list(self, listname):
    """ Takes a listname, checks that the teacher is not a member of the list, then adds it. """
    client = mailchimp_get()
    listid = settings.MAILCHIMP_LISTS[listname]
    if not self.mailchimp_teacher_in_list(listname):
        data = {
            'email_address': self.user.email,
            'status': 'subscribed',
            'merge_fields': {
                'FNAME': self.user.first_name
            }
        }
        client.lists.members.create(listid, data)

def mailchimp_lists_get(self):
    """ TEMPORARY - Returns a list of all mailchimp lists """
    client = mailchimp_get()
    return client.lists.all(get_all=True, fields="lists.name,lists.id")

def mailchimp_teacher_in_list(self, listname):
    """ Returns a bool indicating if this teacher's email address is in the list """
    email = self.user.email
    client = mailchimp_get()
    results = client.lists.members.all(settings.MAILCHIMP_LISTS[listname], get_all=True, fields="members.email_address")
    members = results['members']
    addresses = []
    for m in members:
        addresses.append(m['email_address'])

    return email in addresses

I think the names should be pretty self-explanatory. The mailchimp_lists_get() method was just an experiment based on following the instructions in the documents.

Lastly, I created a view and a URL just to test these functions by printing to the console and looking at the (currently empty) lists in mailchimp.

Summary

It was my first time sitting down and thinking critically about what I wanted and how to do it and I have to say that mailchimp and the library basically made it painless. I have other project ideas that will require more API integration, and it’s made me hopeful.

Preparing for Clowns, or what I learned from a Twitter-fight

The CodeNewbie chat last night was about dealing with bullies, and, since that dovetailed so well with what I’m working on right now, I thought I’d try to be a bit more active in blogging about what I’m doing.

You see, I think my app is 90% finished, but I’m taking a break from features to get ready for the public. Which, really, means, to get ready for the jerks. You see, the worksheet generation app isn’t a social app. The user’s interaction with it is basically all centered on the user and their own groups and classroom resources for their groups.

But, contributions are shared. That is to say, if you need a translation for the word ‘web app’ for one of your groups and there isn’t one in the system, the system just asks you for one. But then, the next guy profits by the translation you added. And you’re profiting from the resources added by others.

The Twitter Fight

But then, I got in a bit of a fight on Twitter. I’ll post sometime on the idea of ‘defending the ancestors,’ and why I don’t like the term ‘the ancestors’ being used as a dog-whistle for racism (racist dogs!) but that was what it was about. And it wasn’t nice.

While I’m a middle-child and can handle Internet strangers being mean to me, I realized that the guy I was fighting with had the advantage: his Twitter handle was only for his trolling. My Twitter handle connected to all my life, and he could see that, use it in a fight. And, because I tend to fear the worst, he could start being a jerk in other parts of my life.

I realized that, when I start posting about the worksheet generation app by name, with links, anybody who felt like they didn’t like me (and there have been a lot of those people in my life) could create a free account, and just throw a wrench in the works by adding terrible resources.

Imagine if a legitimate user — maybe even one paying for the service — wanted that aforementioned translation of ‘web app’ and got something like ‘where we see the naked photos of your mom.’ Even worse, what if that person — like I often do — didn’t proofread that worksheet before handing it out to students. (At some point, worksheets will be emailed with a mouse-click.) What a nightmare.

Worse than paranoia

I talked myself back down from “Twitter people will be mean.” After all, the solution could easily be ‘just charge everyone.’ Who’d pay me money for the privilege of trolling me?

Then I realized I shouldn’t worry about Twitter trolls making accounts and being jerks. I should worry about people just like me thinking that they’re being funny. Or, even worse, thinking that the resource they’re making is appropriate for their students. After all, we teach adults, whose to say that we can’t include “your mom” jokes?

My real  nightmare is someone either testing out the system, playing around, and putting random crap in… Just to see if it really appears in a worksheet.

Of course there’s a solution

I can deal with it. Of course I can. There’s not a problem without a solution.

My interim solution is, for now, giving users the opportunity to explicitly say “this is part of an inside joke with this group.” You can easily add resources that will only be used for a single group. Easy.

The other thing is pretty simple: all resources are immediately available to the person who creates it. Everything else has to be approved, first. As users are added, it will be possible to say “the resources added by user_x will be automatically available,” but, until then, it means that I’ve invested a lot of time already (all morning today was spent creating the interface and the backend that will enable this level of moderation) into the project of investing a lot of extra time in this project.

And that kinda stinks. It makes me like people, as a group, just a little bit less.

Can’t say I’m not learning

I continue to be frustrated by whatever it is that is interfering with my rented Ubuntu server serving up my worksheet-generating django site. I’ve even come so far that I’ve written up my troubles in a Stackoverflow question.

While I’m decompressing, I thought I’d summarize some of what I’ve been learning. A lot of what I did felt like trying things wildly (Is this the right path? Maybe up a level?) and restarting services. Then, I took an answer to this question on Stackoverflow to heart:

You should try to invest a bit of time in understanding all the components involved. For example you have merged the .ini uWSGI file with nginx.conf that is completely wrong. I can suggest you to start from here: http://uwsgi-docs.readthedocs.org/en/latest/WSGIquickstart.html

Try to understand every step (expecially the part about using official sources instead of distro packages). Start deploying without nginx (only uWSGI), and only after you are sure the thing is clear, you can proxy it behind nginx.

I went through the steps in the quickstart tutorial one at a time, writing a short Python program to do ‘hello world,’ and moving forward from there. It was helpful, and I have learned.

For example, I guess I never really thought about it before, but I think that uwsgi is the part of the whole thing that actually runs my code. I’ve certainly seen something like this often enough:

web browser <=> web server <=> wsgi <=> django code

And, to be honest, I never really thought about what ran my code. When you run the development server, it feels clear.

It looks like, whatever I’m doing wrong, I have the uwsgi service incorrectly configured, such that it can’t load my code. That’s obviously a problem. I just don’t know how to fix it (and I’m not even sure that I know it, this is all a question of me reading error logs, googling, reading answers to questions that are only similar to mine, and trying to extrapolate).

Still, in addition to beginning to wrap my head around wsgi, here are some things I’ve learned:

  • A few new console commands in linux, including ‘env,’ ‘useradd,’ and ‘nano’
  • Where some error files are stored ( /var/log/nginx/error.log — I can type that from memory)
  • How to use ‘sudo’ and ‘su’
  • Starting, stopping, and restarting services

There are some things I’d like to know more about, but my brain feels too melted to really absorb new information. They include:

  • How to uwsgi and nginx communicate via sockets? What does that mean? What is a socket file?
  • I only did the bare minimum of ‘hardening’ my server. How vulnerable is it? What are the chances that it will be attacked deliberately, or by someone just attacking every website in the hopes that one is vulnerable?
  • I’ve realized that all of the http://localhost:8000/…/ in my Django templates will need to be updated to use the domain name I bought. How do grown-up developers deal with this? How will I test things here ‘in development’ before sending it to ‘production.’ (I feel like a poser using those words).

So, yeah, I’m frustrated, but not so much that I’ve opened the red wine. (See my last post.) If I had a bottle open, however, I’d raise a glass to learning.

Django!

So, I reached a milestone without ever blogging about it: I ‘perfected’ the desktop version of my software.

Note the quotes. Obviously, there’s a lot more it could do, and the interface couldbe a little less clunky (you can tell I just tacked things on as I made them), but the software does what I wanted it to do when I started writing it: It creates PDFs of vocabulary workhsheets for English learners, entirely using hand-made material, but re-using material that’s been used before.

In fact, someone had the idea to add crosswords and wordsearches, and that wasn’t difficult at all. In addition, it will make a nice, alphabetized vocabulary list for the vocab a group has learned.

Arguably, it does more than I had intended for it to do when I got started. And that can only mean one thing:

It’s time to do the django. Since I got about halfway into the desktop version, I realized that it made more sense as a service than anything else, and that the web was the right vehicle for distribution. Imagine if material that I made for my classes could be used in other classes. Imagine if material that my friends and colleagues create for their classes could be leveraged in my classes?

The goal, pure and simple: make it seem like I’m working harder for my students than I am. And the web is the way to do that.

So, I’ve started trying to create a web interface using django. And I’ve been mostly successful. It seems as though very little that I wrote for tkinter can be directly ported without extensive re-writing, but, since I already know how it ‘works,’ I don’t have to figure out how to do it, I just have to adjust it.

Right now, a lot of basic functions have been taken care of, and I have to do a lot of the ‘filler’ work that isn’t so much difficult as… plentiful.

So, look for me to write a bit more about that as time progresses. As well as my insecurities as I go from the pride of working with software that I created myself, to the insecurity of asking my friends to take my software for a spin.

How XML Works

So, this will obviously not be an explanation of how XML works. Instead, this will be a short explanation of “what I think about when I think about XML.” And, I want to begin by saying that I am happy that this page here exists, as that’s where I went to double-check my vocabulary and, in the process, learned quite a bit.

XML basically is a standard by which your computer (or a Python library, to be specific) knows how to put information into text format to save to a file. If you’ve ever done much by way of HTML encoding, the basic formats will look familiar.

How to write XML.

Remember in the last bit where I said that I think of XML data as a pile of folders, some containing other folders? Well, basically, each folder is an Element. It has this format:

<ELEMENT /> or <ELEMENT>…</ELEMENT>

They both are elements named ‘ELEMENT’ (I don’t know why I do so much in all caps in my own XML, I guess it’s so that I know I’m writing for a computer and not a person.)

Of the two, the first is a self-closing tag. (That’s just the way of saying that no second tag is required to ‘close it’, like in the second example.

In the second example, the first tag ‘opens’ the element, the second ‘closes’ it.

The reason for opening and closing the element is pretty simple. Remember my example of a bundle of nested folders? Well, if we have an element meant to represent a user, it might also contain things like grammar this person has worked on, or a list of vocabulary that they’re using.

To include another element in an element, you open the element, create the other elements, and then close the element again. Like this:

<USER NAME=”Toby the Amazing”>

<VOCAB_LIST WORDS=”XML, pizza, whining” />

<PRACTICED_GRAMMAR>

<GRAMMAR NAME=”Simple Past” />

<GRAMMAR NAME=”Simple Present” />

</PRACTICED_GRAMMAR>

</USER>

(I’m sorry for the formatting, but I’m not willing to do all the weird stuff required to make it look nice with WordPress.)

So, if you look at what I have above, there is a user, and inside the user element (the first and last lines open and close the element) there is an element containing a vocabulary list, as well as another element for practicied grammar which contains more elements, each for a specific grammar that has been practiced.

If you look at the different elements in my example, within the tags, or within the opening tags, you’ll see where I wrote NAME=”Toby” or WORDS=”XML…”. These are attributes in the element.

Going back to my paper metaphor, I think of opening and closing tags as the folder that contains something, and self-closing tags as individual pieces of paper in that folder. So far, so good. The thing is, the attributes are what you’ve written on each folder/paper. So, in the folder you might have a hundred copies of a form that gives information on a grammar practiced (I could not name a hundred forms of grammar, that’s a bad example.)

But, just as it makes no sense to have a folder on which you have “Name: Toby” and “Name: Liam” written (which name is it?!) you can’t have two attributes with the same name in XML. I don’t know what happens when you manually write XML that way, but in the python library I’m going to write about next, it would just change the name.

Remember this: If you want many of something — grammar forms, words, users — what you want are elements. If you want to define your elements with unique properties, name, height, level of charm, then you want attributes.

Don’t worry, you won’t have to write XML

I went through all of that, and you’ll probably never write XML in your life (though I do write the initial tags in the XML files that I use, mostly because I’m too lazy to do it with code.)

In the next little bit that I write, I’m going to explain how I do XML in Python, and you’ll need to know the words element and attribute, but you’ll never have to worry about how to write it. After all, the goal is to have your computer do the work, not you.

Still, one of the great things about XML is that you can manually edit it, and I think it’s good to know how it’s put together.


This is something I’m writing mostly for my nephew in support of the Typing Tutor challenge that I’ve issued to him. I imagine all of this information has been explained better elsewhere on the Internet.

So why are you reading this?