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

Virginians: You mad yet?

After decades of grousing about service, family values, the rule of law, executive restraint, and the dignity of the Office of the President, Republicans elected a draft-dodging, sexually abusive philanderer who has no understanding of the Constitution, boasts about all his executive orders, and embarrasses the nation with his childish tweets and his reality show cabinet of hired and fired loudmouths.

Are you mad enough to do something about it yet?

I ask because the goon who now soils the world’s most dignified office has a minion poised to do the same to the highest office in Virginia.

You can stop it by voting. And when you vote, don’t stop at Governor. Because every Republican victory only encourages Republican hatred, lies, and embarrassing behavior.

Sure, a lot of Republicans are decent human beings. But what good is a decent Republican who doesn’t denounce the hatred and lies of their party’s leadership? What good is a decent Republican who supports the lie that smart immigration policy is the same as supporting vicious gangs? What good is a decent Republican who doesn’t stand up to the NRA as they distort the meaning of our Constitution to make money from the blood and fear of American citizens? What good is a decent Republican who will throw away federal funds for Medicaid expansion just to spite hard working Virginians who might benefit from it?

This is the year for real Virginians to show the wold that we’re smart enough to see the truth behind the lies and bring back some dignity to hour highest offices. This is the year we get to the polls and vote for Democrats up and down the ticket.

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

Colaw Will Dig In and Find Answers

The forum that I wrote about earlier is over and done with so it should be time for me to leave it alone and move on. But Ryan Kushner’s article about the event in the October 18th edition of the Smithfield Times is slanted to make Rebecca Colaw seem weaker when she is by far the stronger candidate, and I want to set the record straight.

For example, Emily Brewer did not answer the question about background checks. But like Brewer, the article is unclear about her response, saying,

only Colaw suggested the need for an end to purchasing firearms at gun shows without a background check, Brewer stating that there was already a provision in place that allowed sellers to refuse a sale at the shows.

Mr. Kushner quoted part of her reply and left open the possibility that somewhere in the rest of her response was an answer to the question.

Ms. Brewer also avoided answering the question about women’s reproductive health.

The only editorializing in the article is where Mr. Kushner describes Ms. Colaw’s stance about Confederate monuments as “less firm” than Brewer’s. But Ms. Colaw’s response was amazing. She spoke to African American groups and The Sons of the Confederacy and found common ground. She said that neither side wants to pay for removing the monuments. She also said that each monument has its own circumstances that would have to be addressed because, for example, some are on public grounds and some on private. So indeed, Ms. Colaw’s response might be “less firm” than taking a side on an issue without doing any research, but her response is far more balanced and truthful, and shows the kind of effort Rebecca Colaw will put into addressing the issues that affect Virginians

Rebecca Colaw’s response to free community college was also intelligent and revealing of the amount of effort Ms. Colaw will put into addressing our issues. She said she was against public funding of community college until she looked at how Tennessee implemented it. She said it would add to the tax base by creating more and better paid taxpayers. She also said she would research the details before pushing for it. But her answer was glossed over, and she was made to sound as if she supported free college without any thought of the costs and benefits.

Rebecca Colaw is what our community needs. Emily Brewer stuck to talking points. Rebecca Colaw spoke truth to opposition and provided data to back it up. She proved her willingness to hear different sides of controversial issues and to dig in and find answers to complex problems.

Rebecca Colaw Supports Background Checks. Her opponent didn’t say.

At a forum in Smithfield, Virginia, the candidates for Virginia’s 64th House of Delegates district were asked about their position on background checks. The Republican candidate Emily Brewer said that she supports the Second Amendment. She said that gun sellers have a right to refuse to sell to anyone they don’t want to. She said she supports the right to carry. She said nothing about background checks.

Rebecca Colaw, the Democratic candidate, was clear: She supports them. She said that as a lawyer, she knows where criminals get guns. They get them from gun shows.

In 2013, Emily Brewer tweeted her support for President Obama’s executive order on background checks, and now she won’t mention background checks while answering a question about them.

I find it disturbing when a politician who knows what’s right is afraid to say so.

Background checks won’t prevent every tragedy and won’t stop every criminal or mentally ill person from getting guns. But they’ll make it harder. Some will get caught using fake ID’s and some will get caught because their mental state prevents them from understanding the consequences of trying to get a gun. Others will have to work harder to arm themselves or supply guns to others.

Rebecca Colaw is a gun owning Democrat who believes in our right to bear arms and clearly states her support for background checks. Most Virginians support background checks, too. We should work to elect candidates who agree with us and aren’t afraid to say so.

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.