In Django, Effectively Allowing Addition and Removal of Related Models

I’m very new with Django and am happy to accept criticism or suggestions on better ways to do this.

The use case for what I’ve done is this: I’m working on a database to track membership for a small political committee. One of the things we’re interested in is the districts in which our members live. There are different types of districts that we want to track including voting precincts, municipal boroughs, state house districts, state senate districts, and U.S. congressional districts.

For creating or editing a person’s information, there should be a single select box for each type of district. But I want admins to be able to add and remove district types through the admin panel.

To make this work, I have four models: Person, District, DistrictType, and Residency. Residency is an intermediate model for Person and District, but I defined it as a class instead of letting Django create it.

Here is a very simplified version of my models:
Models.py

class DistrictType(models.Model):
    name = models.CharField('Name', max_length=30, help_text='The name of the type of district')

    def __str__(self):
        return self.name


class District(models.Model):
    name = models.CharField('Name', max_length=30, help_text='The name of the type of district')
    district_type = models.ForeignKey(DistrictType, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return self.name

class Person(models.Model):
    name=models.CharField('Name', max_length=30, help_text='The person's full name'

    def__str__(self):
        Return self.name

class Residency(models.Model):
    district = models.ForeignKey(District, on_delete=models.CASCADE, help_text='The district in which the person lives')
    person = models.ForeignKey(Person, on_delete=models.CASCADE, help_text='The person who lives in the district')

    def __str__(self):
        return self.district + ': ' + self.person

 

I want admin to be able to add and remove districts and district types.

admin.py

from .models import District, DistrictType

class DistrictAdmin(admin.ModelAdmin):
    list_display = ( "name", "district_type")
admin.site.register(District, DistrictAdmin)

class DistrictTypeAdmin(admin.ModelAdmin):
    list_display = ( "name")
admin.site.register(DistrictType, DistrictTypeAdmin)

 

With District and DistricType registered, admins can add and remove district types (and districts) as needed.

I want users to be able to add and edit people without using the admin panel, and I want the person form to have one select box for each type of district. Of course, I have permissions set to restrict who can add and edit people, but I’m not covering that here.

If, for example, I have four types of districts, I want four select boxes on the person form. Each of those select boxes represents a residency. So I have to define a residency form to be used as an inline model form.

forms.py

class PersonForm(ModelForm):
    class Meta:
        model = Person
        fields = (
            "name",
        )

class ResidencyForm(ModelForm):
    district_type_name = forms.CharField(required=False)
    class Meta:
        model = Residency
        fields = (
            "district",
        )

 

Note that district_type_name is not part of the residency model and won’t be used in the update or creation of the object. The reason it is there will be explained below.

The residency model has two fields, person and district, but only district is needed here because person will be taken care of by the formset factory.

views.py

class PersonCreate(CreateView):
    model = Person
    form_class=PersonForm

    district_type_all = DistrictType.objects.all()
    district_type_count = DistrictType.objects.count()

    ResidencyFormSet = inlineformset_factory(Person, Residency, form=ResidencyForm, extra=district_type_count, max_num=district_type_count, fields='__all__')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        residencies = self.ResidencyFormSet( instance = self.object )
        for i in range( self.district_type_count):
            residencies.forms[i].fields['district'].queryset = District.objects.filter(district_type__id=self.district_type_all[i].id)
            residencies.forms[i].fields['district_type_name'].initial = self.district_type_all[i].name
            context['district_label_' + str(i)] = self.district_type_all[i].name
        context['residencies'] = residencies

        return context

    def form_valid(self, form):

        self.object = form.save()

        context = self.get_context_data()

        residencies = context['residencies']
        if residencies.is_valid():
            residencies.save()

        return super(PersonCreate, self).form_valid(form)

 

If you don’t understand inline formsets, I think Daniel Chen’s post will explain their use a lot better than the Django docs.

With the line

ResidencyFormSet = inlineformset_factory(Person, Residency, form=ResidencyForm, extra=district_type_count, max_num=district_type_count, fields='all')

, I created a set of inline forms. The first parameter, Person, defines the parent model. This is why I don’t need a person field in my form definition. The extra parameter defines how many inline forms to display. I want that number to match the amount of district types.

The max_num parameter ensures there will be no more forms displayed than the amount of district types. It’s not necessary in the creation view, although I included it here, but it is necessary in the update view. Without it, there would be four extra select boxes in addition to those that already have data from the previous update or creation.

At this point, I would have four identical inline forms, each with a select box that includes every district in the database. But I want to limit each select box to a certain district type. This is where I think I’m breaking new ground.

Here’s another look at get_context_data in views.py

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        residencies = self.ResidencyFormSet( instance = self.object )
        for i in range( self.district_type_count):
            residencies.forms[i].fields['district'].queryset = District.objects.filter(district_type__id=self.district_type_all[i].id)
            residencies.forms[i].fields['district_type_name'].initial = self.district_type_all[i].name
        context['residencies'] = residencies

        return context

 

In get_context_data, I go through each of the residency forms in the formset, and update the queryset for it’s select box. Now each select box is limited to districts of a certain district type.

Here’s where that extra district_type_name field comes in. The default label for the district field is “District”. But on the form, I want each label to be the name of the district type that the select box is limited to. So I populate the unbound field district_type_name with the name of the district type, and use that value as the label.

From person_form.html in my templates directory


    {% csrf_token %}
    <div class="section" >
        Name
        <div class="row" >
            <div class="label" >
                Full Name
            </div >
            <div class="fields" >
                <div class="field" >
                    {{ form.given_name }}
                </div >
            </div >
        </div >
    </div >

    {{ residencies.management_form }}
    <div class="section" >
        Districts
        {% for form in residencies.forms %}
            <div class="row" >
                <div class="label" >
                    {{ form.district_type_name.value }}
                </div >
                <div class="fields" >
                    <div class="field" >
                        {{ form.district }}
                    </div >
                </div >
            </div >
        {% endfor %}
    </div >

    

Here I used district_type_name.value as the label for each select box

Advertisements

Batch File to Maintain Wireless Connection

This is a Windows batch file and a hack for working around intermittent wireless disconnects. If your connection periodically goes to “no internet access”, this script may help. Obviously, it would be better to find and fix the problem but this may keep you going until you do.

This seems so easy that there must be something wrong with it, but I don’t know what that is yet, so I don’t promise anything. It works for me and I can even keep a putty session going with this script running despite frequent intermittent disconnects.

My script figures out the default gateway and the SSID. It pings the default gateway and upon failure, it will reconnect using the SSID. Then it waits 30 seconds and starts over.

I came up with this script after finding plenty of scripts that were pretty close but not quite what I wanted. Some required that you figure out the default gateway and/or the ssid before running the script. Some required the name of the interface. I wanted something that didn’t require my input.

This will open a command window. The command window reports when a reconnection was required and also allows you to hit a key to bypass the 30 second wait period for one round.


@setlocal enableextensions enabledelayedexpansion
@echo off
:loop
for /f "tokens=3 delims= " %%a in ('netsh wlan show interfaces ^| findstr "^....SSID"') do (
set ssid=%%a
)
for /f "tokens=13 delims= " %%a in ('ipconfig ^| findstr "Default.Gateway.*[0-9]"' ) do (
set gateway=%%a
)
ping -n 1 %gateway% | find "TTL="
if errorlevel 1 (
goto :reset
) else (
@timeout /t 30
goto :loop
)
:reset
time /T
netsh wlan connect %ssid%
@timeout /t 60
goto :loop
endlocal

Take a Look at These On Line Scams

This screen popped up after I clicked on a link on Reuters. It looks like an official page from Adobe, telling me I have to install the latest version of Flash. It is not. At the bottom, there is a disclaimer telling us what it is:

We are not affiliated or partnered with Adobe […] This offering is for a download manager that will install independent 3rd party software that will update the advertised program.

Flash Scam
I do believe that if I download and run the installer, it will in fact install the latest version of Flash. I’m sure it will also install applications that deliver a steady stream of popup ads. It will probably hijack my browser and prevent me from using Google, instead delivering a bunch of paid-for results whenever I try to search for something. It might do even worse than all that.

But it looks so real. Here’s another example.

This is a page from Sourceforge, a big repository for open source projects, and it’s the Sourceforge page for Xming, a server which allows you to run Linux X applications from a remote server on a Windows desktop. It’s OK if you have no idea what that means. Xming isn’t the problem. The problem is those “Regular Download” and “Premium download” buttons on top. They have nothing to do with Xming and almost nothing to do with Sourceforge. Those are part of an ad. The real download button is the green one closer to the center of the screen. If you click one of the buttons on top, it will take you to another page where you can download another malware installer like the one disguised as the flash updater.

So why Doesn’t Sourceforge do something about these scammer ads on their website? Probably for the same reason I don’t do anything about the ads that may appear on this blog. We don’t see them. In my case, I have nothing at all to do with them. Whatever ads appear on this site are delivered by WordPress, not me. In the case of Sourceforge, they’re just renting space out to Google Ads, and Google Ads is probably working with other companies. Sourceforge has about as much to do with the scammers as your mail carrier does to the scammers who send junk mail to your door.

At any rate, they’re getting trickier out there. They’re doing a good job making their spamware and spyware installers look official, so be sure to double check what you’re clicking on before downloading anything.

My Upgrade to 8.1 is Not Going Smoothly

Update 2013 October 29: And the solution is here: http://blogs.technet.com/b/dennis_schnell/archive/2013/08/31/windows-8-1-wifi-showing-quot-limitied-quot-or-quot-no-internet-access-quot.aspx?PageIndex=2.
To summarize, if you can’t get online after upgrading to 8.1, or if you can’t get online from certain access points, then this might be the solution: Click the link, search for “Kyle”. Read Kyle’s answer then read the paragraph that starts with, “Hey Kyle! You are the man”. Choosing the Broadcom driver worked for me.

Here’s what happened after the original post: As I said in the original post (below), I refreshed the laptop. That dropped me back to Windows 8.0. I waited until I was at school with some free time before trying to upgrade again, so if it failed again I would be able to use a school computer to chat with a tech. It failed again. This time, the tech said, “Unfortunately, the driver for Windows 8.1 isn’t available [from Acer]”, and suggested that I download the latest driver from Broadcom. But I couldn’t find the latest driver on Broadcom’s site. What I found was a forum reply from August

Hi,

Is there a driver that works properly for Windows version 8.1 preview 64 bit.

for BCM57780

Can you please provide me the link to the correct 64 bit driver?

I am always getting disconnects etc

Thank-you,

Shawn

Sorry, no drivers yet for Windows 8.1, they won’t be available until around the time the OS is available at retail. If you’re having problems with the in-box driver then report it to Microsoft so they’re aware of the issue.

Dave

So I used Kyle’s suggestion and seem to be up and running with 8.1.

Below is the original post.

I upgraded from Windows 8 to Windows 8.1 because Windows Store offered the free upgrade in a big purple tile. Then I couldn’t get on-line as school, and I ended up refreshing Windows IAW advice from Acer’s tech support.

Update to Windows 8.1 for free

I started the upgrade while at school but it took so long that I paused it and finished it at home. Everything seemed fine until I got back to school the next day.

Although I could get online at home, I couldn’t get an IP address from either of the schools’s networks. I tried a few things on my own, asked advice from friends, and then spent an hour or so with Acer in a couple of different sessions over two days. The tech in the last of those sessions gave up and advised me to refresh the laptop. That’s not nearly as bad as wiping your hard disk and reinsalling everything, but it’s still a pain. Refreshing saves your files and re-installs the original configuration plus your Windows Store purchases, but you’re on your own for re-installing any programs you got outside of Windows Store.

The refresh took me back to Windows 8.0, and I will try to upgrade again. I’ll let you know how it goes.

First day with a Win 8 laptop

I ordered and Acer E1 with a third generation (aka Ivy Bridge) Intel i5 processor and Windows 8. It arrived yesterday. Almost certainly, I should have waited a couple of weeks longer as prices are likely to keep falling through August, but ordering the laptop was the only way to stop myself from obsessively checking for deals. If you want a laptop with a 3rd generation processor, the first weeks of August are probably the best time to buy, if you look for deals. My laptop has a 3rd generation i5 processor, 4 gigs of RAM, and a 500GB hard disk, and I got it for $399 plus shipping (after a mail in rebate) from Tiger Direct. It does not have a touchscreen. Prices are falling so dealers can make way for the 4th generation (Haswell) processors. If battery life is important to you, you might want to pay the extra money and hold out for a Haswell.


Acer Aspire E1-571-6837 3rd Gen i5, 4GB, 500GB, from Tiger Direct: 449.99, 399.99 after Rebate

While looking for deals, don’t get tricked into buying a 2nd generation (Sandy Bridge) chip or older. If you get a great price on an older chip and it’s what you want, that’s fine, as long as you know what you’re getting. The generations are marked by the first number after the dash. My chip is an i5-3230M, and it’s the first “3” that designates the generation. Also don’t get fooled by a “5” after the dash, followed by two more digits instead of three more. Those are actually older.

The i5 seemed like a good match for me. I’m on too much of a budget for an i7 and I’m not a gamer. An i3 is better for budget buyers who don’t run a lot of intensive applications. Budget buyers who will primarily use their laptops for email should also consider the very low prices available on 2nd generation Intel chips, Pentiums, Celerons, and several others out there. AMD makes comparable chips to Intel. The AMD A8-4500M seems to be at about the same level as the Intel i5. As with Intel based computers, be careful about chip model numbers.

One of the first things I tried to do was load Ubuntu 13.04 in a dual boot configuration, but the Ubuntu setup didn’t recognize the existing Windows 8 installation and wanted to format the disk as if it was empty. There’s plenty of information on-line about working around that problem, just like there’s plenty of information about working around the new UEFI security feature, but the less then perfect installation start and the difficulties I’ve read about overcoming the UEFI feature were the last of a dozen or so reasons that made me decide, for now at least, to leave the laptop configured as a single boot Windows 8 machine. Instead, I installed Virtualbox and loaded Ubuntu on a VM.

So I’m succumbing to Microsoft for now. My first impression of Windows 8 is: I like it. The negative backlash against Windows 8 is wrong-headed but I do appreciate the affect that it’s had on prices.

Windows 8 is surprisingly keyboard friendly. Even though it’s designed for touch, I can hit the Windows Key and then type a command or part of a command, like “chrom” or “word”, and get a list of matching applications. It works better than hitting Alt F2 in Unity or Gnome. I can also navigate the start screen easily with the keyboard arrows, and I can shutdown the system without touching the mouse or touchpad by hitting ctrl-alt-delete, then using the tab key to get the power icon. When using the keyboard is easier in Windows then in Linux, it might be time for a shift in thinking.

Windows 8 results from typing 'word'

I also like the live tiles.

I’m still wary of using Windows as my primary OS. I spend a lot of time fixing bugs and removing spyware from my wife and daughter’s computers. My son uses Linux and I never had to fuss with such problems on his laptop. But I like Windows 8 so far. I like the UI more than the UI of previous versions of Windows and I’ve gotten very frustrated with Unity and Gnome. I’m willing to give Windows another go.

So, so far so good. As of Day 1, I’m happy with my Acer and I’m happy with Windows 8.

Update (same day) I’ve already had to remove adware extensions from Chromium. I was getting ads when I clicked on links, and while poking around the settings found an extension called “tidynetworks”. I removed the extension but I don’t yet know if I’ll have to do more to properly get rid of it. I also had something called webcake and I removed that as well.

Cable Signal Loss Causes Modem to Disconnect Router

I’m crowd sourcing this among the three or four people who read this blog. And it might help someone who’s experiencing similar symptoms.

If you’re experiencing intermittent disconnects between your modem and router, it may be a problem with your incoming signal.

It makes no sense to me either, but when my cable modem loses the signal from my provider it disconnects the router. The router’s status changes from “connected” to “connecting”, and of course the modem’s status page is inaccessible from wireless network. What I thought was a problem between the modem and router was actually caused by a poor signal from the provider. It’s a Motorola Surfboard and a Medialink router, but this also happened with a Cisco modem which the cable company replaced and old Cradlepoint modem.

Any ideas why this might happen?