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

Don’t let the Smithfield Times Bring you Down. Get Out and Vote!

This will be my second post criticizing The Smithfield Times. I was pleased with Ryan Kushner’s response to my previous criticism, but am now once again disappointed with the Times’s unfair reporting.

In the “Our Forum” section of the Smithfield Times, the Times states that the race for the 64th Virginia House district has already been decided because the district is “solidly Republican”. They don’t say who the better candidate is, but can only muster lukewarm support for the presumptive winner, Emily Brewer.

She is personable and engaging, and while much of her literature has checked all the party-line blocks — less abortion, more guns, fewer taxes — she strikes us as an intelligent and reasonable person who will quite likely listen to all of her constituents. The 64th District could do a lot worse, and on occasion, has.

It would be nice for the Times to elaborate on why they feel the candidate who sticks to party line talking points might listen to all of her constituents. If Emily Brewer has spoken to anyone other than her base supporters, why doesn’t it show in anything she’s said or written? By contrast, Rebecca Colaw has met with Confederate as well as African American groups before forming an opinion on monuments. Colaw has been knocking on Republican and Democratic doors during her campaign, and like most of the people she has spoken to, supports background checks while vowing to fight more restrictive gun control measures.

Before partisan redistricting, the 64th elected Democrat Bill Barlow ten times in a row, and Democrat Hardaway Marks was elected five times before him. Many of the voters who elected Bill Barlow counted themselves as Republicans but crossed party lines to elect the more qualified candidate. Many still live in the district and have a chance to do so again.

Rational Republicans and Democrats alike can only elect the more qualified candidate if they vote. They should avoid being discouraged by the Smithfield Times’s undemocratic resignation that the candidate they tacitly support has already won.

Ryan Kushner’s Reply

Ryan Kushner of the Smithfield Times responded to an email which was similar to my previous post. I appreciate the courtesy and what I think is the honesty of his reply, and feel I owe it to him and to the handful of people who read what I write that I post his words without further comment.

Hi Mr. Goldberg,

I really appreciate you reaching out. I agree with most of this.

I personally did not perceive that being “less firm” on the subject of the statues was a negative, but I can see how one might take it that way. Ms. Colaw did not give a firm answer as to whether she believes they should be removed or not (which, again, is not necessarily a negative thing), but said that she was intent on seeking a compromise, which was stated in the article.

Ms. Brewer did answer the question about background checks at gun shows in her response (whether it was a good answer or not), which I also noted in the story. We don’t have control of what someone might presume she may have also said if it wasn’t included in the story.

As for free community college, while I couldn’t fit the whole response into the article, I did note Ms. Colaw’s intention to further study the state budget to research whether it was possible.

Again, I thank you for the email. I always appreciate feedback, particularly in summaries of political events. If you’d like to send your thoughts as a letter to the editor, please feel free to shoot it over to editor@smithfieldtimes.com.

Best,

Ryan Kushner
Staff writer
The Smithfield Times
228 Main St.
PO Box 366
Smithfield, VA 23431
Phone: 757-357-3288
Fax: 757-357-0404
Email: rkushner@smithfieldtimes.com
Website: http://www.smithfieldtimes.com

I dislike kneeling protesters; but I dislike racist murderers more

Often, the choice isn’t between something you like and something you dislike. It’s often between something you dislike and something you dislike more.

I dislike national figures protesting during the National Anthem. But I dislike more a president who has harsher words for them then he does for white supremacists. I dislike failure to properly show respect for our nation’s symbols. But I dislike more when people, especially those who never served, misinterpret a silent demonstration as a direct insult to those who lost their lives or abilities to preserve our right to protest.

I also dislike the lack of respect for our nation’s many police officers, most of whom are self-sacrificing public servants who risk their lives for the protections of others. But I dislike more the idea that innocent people killed by racist murderers in uniform isn’t a problem that needs to be addressed.

So as unpleasant as I find Colin Kaepernick’s decision to publicly shirk his show of respect for our National Anthem, I’ll take his side over the side of people who would rather he didn’t do so and also rather the issue he has brought attention to not receive that attention.

It’s unpleasant taking the side of people who get payed much more than they should for playing a game. It’s not the side I like. It’s the side I dislike less.

Last Leper in the Colony

I hope I can be forgiven for being a little apprehensive when a friend or an acquaintance who is not an author asks me to read a book that he has written. But ‘Last Leper in the Colony’, by Wynne LeGrow, MD is clear and engaging, and an important read for anyone interested in religion and politics. Wynne has a knack for describing the ironic and an honest way of describing his own fears and shortcomings that will warm the reader to him and keep the reader looking forward to the next page. No matter what your faith or how strong your convictions are, the influence of religion on politics despite the founding fathers’ efforts to keep them separate, is an important topic that should not be ignored.

20170804_101227

If You’re Not Freaking Out about Net Neutrality…

(image is just text) Urgent: If you're not freaking out about Net Neutrality right now, you're not paying attention

I let July 12 sneak up on me.  I meant to do more to get ready for the Net Neutrality protest but today is July 12 and I hadn’t mentioned it to my friends or even brought it up at the Democratic Committee meeting last night.  Part of the reason – but only part – is none of my family or friends are talking about Net Neutrality.  I don’t see much about it on my Facebook feed.   Net Neutrality is way in the back of people’s minds and a lot of folks aren’t even sure what stand they should take on this issue.  Right now we’re obsessed with Junior’s collusion with Russia to dig up dirt on Clinton.

For those who aren’t ready to jump on the left side of every issue, Net Neutrality might seem like just another case of Liberals asking government to step in and regulate something which might better be guided by the invisible hand of the free market.  I understand that argument.  I think we on the Left generally want too much regulation and before the Right went insane, they provided a rational voice of opposition to our excesses.  There’s nothing  wrong with having someone say, “Don’t you think you’re spending too much money on this issue?”, or “Don’t you think those folks would be better off solving their own problems?”, or “All those regulations are going to make it too hard for people to do what you expect them to do.”

But that rational voice of opposition has gone rabid.  And without that rational voice, people on the Left should occasionally ask themselves, “Is this something that requires more government regulation?”, and we need to train ourselves to say “No” more often than we currently do.  But when our primary source of information is in danger of being overtaken by corporate interests who would be in control of what news and opinions we get to hear, then we should fight to keep that from happening.  I believe our Founding Fathers would agree.

Article 1 Section 8 of our Constitution gave the Federal government the power to establish post offices and post roads.  Early on, the leaders who shaped our nation knew that the government had a role in ensuring that the people had fair access to information.  That role still exists now that the Internet has become our primary postal route.  Allowing corporations such as Comcast or Verizon to throttle the route for some sources of information while keeping it wide open for others would be in violation, at least in spirit, of the guarantee of free flow of information that the framers wrote into the Constitution.

If corporations control our information, they can control us.  We can’t protest abuses of power if we don’t know about them.  We can’t organize if we can’t communicate.  We’re about to cede control of our information to the companies that own the routes through which that information flows.   

Imagine not being able to access the Washington Post or the New York times to learn that Donald Trump Jr was told, in an email, that he would be meeting a “Russian government attorney” to provide dirt on Clinton as “part of Russia and its government’s support for Mr. Trump”.  Imagine not being able to read how Republicans are “Taking a sledgehammer to Medicaid” .  

This is why we have a 1st Amendment.   If we lose fair access to information, we lose one of the most fundamental aspects of a free society.  Let’s not let that happen.  It’s easy to let the Net Neutrality issue slip by.  It’s wonky and technical and hard to directly equite to such issues as health care, voter suppression, the environment, or war.  But it’s central to all of those issues.  

So do me a favor.  I’m feeling pretty guilty for not doing more up until now.  Go to www.BattleForTheNet.com and sign the petition.

Thank you.

Update (7/13): Comcast says they support Net Neutrality, but not “reclassification of broadband as a telecommunications service under Title II”. The verge says Comcast, Verizon, and AT&T want Congress to make a net neutrality law [ as opposed to regulation ] because they will write it.

It’s Not Hypocritical To Review the Election

A commenter on an article in the Independent about how the election might have been rigged wrote this

Liberals lost, they should accept the result. They slated Trump when he said he may or may not accept the result. Severely damaging to democracy to challenge the result they said…except when liberals do it eh.
Turn the result around now, legitimately or not, and all hell would break lose.
This is the most hateful election I’ve ever experienced.

Bullshit. The problem with Trump’s accusations of rigging are that he had no evidence. Even before the election, as he was dropping in polls, he was said the only way he could lose was due to rigging.

We’re not even making accusations yet. Looking at evidence before making accusations while condemning someone for making accusations without evidence is not hypocrisy. It’s the opposite of hypocrisy.