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

Redirecting a Request with Path Information

bnmng.com is mapped to my share on totalchoicehosting‘s servers. But users can still type bnmng.com in the address bar and get redirected to the WordPress blog along with any extra path information. So http://bnmng.com/category/computing/ becomes https://bnmng.wordpress.com/category/computing.

I do this with a simple index.php in the home directory:
index.php
<?php
header('LOCATION: https://bnmng.wordpress.com' . $_SERVER['REQUEST_URI']);
?>

$_SERVER[‘REQUEST_URI’] is everything after the .com. In case the code wraps on your screen, this is just one line of code between the <?php and ?> tags.

But this wouldn’t work unless I ensured that my index.php file would respond to the url with the path info.

To do that, I copied WordPress.org‘s .htaccess file (described here as “genius”, and explained in more detail here), which is found on self-hosted WordPress blogs. This requires Apache’s mod_rewrite.

.htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php  [L]
</IfModule>

Without this, http://bnmng.com/category/computing/ would simply return a “page not found” error.

That’s all I need to make the redirect. The redirect only occurs if the request is bnmng.com without any path information or bnmng.com with path information that doesn’t correspond to anything on my share. http://bnmng.com/codeexamples, for example, doesn’t get redirected.

This also seems to work for any bnmng.com feeds that people may have subscribed to before I moved bnmng.com from wordpress.com to totalcoicehosting.com. There’s no need to create a feed directory on totalchoicehosting, as I described in an earlier post.

I could have just put a self-hosted blog in my home directory and had a smoother effect without the redirect, but I like the convenience of using WordPress.com’s servers. And if they make a little money with ads on my blog then I’m happy to support them, especially if it doesn’t cost me a dime.

Blog Reader / Post Sharer

I’ve been experimenting a way to share posts on my sidebar. Rather than a static blogroll which just lists blog titles or an RSS feed which shows every post from the feed, I want to specifically select the posts that appear on the sidebar. I don’t know if there’s already something out there that does this.

I’m working on a blog reader which lets me select posts. When I choose to share a post, it writes an XML file which includes only those selected posts.

On my blog, I have an RSS widget which reads the file that I created.

My source is a yahoo pipe:

I use the regex module to add a feed title to the beginning of each post. Doing this is useful for a mashup because the feed title would otherwise get lost.

But the source can be any valid feed, either a mashup or a feed from a single blog.

What my reader does is displays all posts from the source feed, each with a “Share” button, which, when pressed, recreates my XML file. The XML file is an RSS feed which only includes posts which I have selected. It doesn’t include posts which have scrolled off my feed, so this isn’t like a bookmarking service which will save bookmarks indefinitely.

The result is on my sidebar, titled “Selected Posts from my Blogroll”.

It only updates when I use it, so my sidebar will be stale if I don’t keep up on it.

The server that this all runs on is at suffolkian.com. My xml output file is http://www.suffolkian.com/bnmng_blogroll/selectposts/selectedposts_bnmng.xml

I’m still just experimenting, but if you want to use this application or help me test it, use the contact form and let me know your user name, email address, desired password, and source feed. This is real low security, so you probably shouldn’t use the same password that you use for your online banking. I’ll make a configuration file for you and email the link to your reader and the link to the xml file that the reader generates. I make no promises that it will work or that you won’t loose your saved posts as I make changes to the code.

Feed Combining: Yahoo Pipes / RSS In Page

On Tina’s old blog, which is still online but not updated since she moved to WordPress, there is a blog roll on her sidebar which shows her blogs in order of most recent update. I can’t find anything that works as well for WordPress. There are various feed combiners for WordPress, but all that I found leave out the name of the blog that the item comes from. And I wanted that to be included.

The best way I found was to use Yahoo Pipes, an online feed combiner from Yahoo. I use the RSS output from Pipes as the source for WordPress’s standard RSS module. Pipes is difficult to understand at first, but it has a video tutorial that should be enough to get a novice started. More advanced operations, like what I’m about to describe, are for people who are more experienced with internet applications.

What I like best about Pipes is the Regex module, which allows me to alter the feed of each item before it gets combined with other feeds. I use the Regex module to put an identifier in the title of each item.

The Feed module gets piped to the Regex module. In the Regex module I select item.title, and replace ^ with the name I want to use followed by a colon and a space. So “The Apocalypse Trade” becomes “Krugman: The Apocalypse Trade”. (^ is Regular Expression syntax for the beginning of a string).

I pipe all of the Regex-modified feeds into a Union module, then pipe the Union module into a Sort module. Finally, the sort gets piped to the Pipe Output. An example of the output is in my main blog.

Yahoo Pipes Editor

To get the RSS feed, I click “Back to My Pipes” and select the pipe I want, then click “View Results”, then “Get as RSS”.

The Regex module has much more capabilities than what I’m using it for and Pipes has more modules than I’ve explored.

Gigaom has a more in depth article.

The interface is a little hard to get use to and Yahoo Pipes intermittently fails to retrieve a feed, but it’s power and versatility make up for any shortcomings.

For a tool that works in wordpress.org and doesn’t require an external combiner, use Rss in page. RSS in Page does not provide a new feed, but it will embed the output of a combined feed into a page or text widget. There’s no graphical interface but if you’re familiar with using shortcodes and can learn some simple formatting commands it’s very versatile.

An improvement, and another use for the safetags function

A while ago I wrote a function which checks HTML snippets and ensures that tags which should be closed are removed unless they are actually closed. For example, if user types

<div style="whatever">Blah Blah

and forgets to close the div tag, then that div tag will be removed. Sure it would be better if the div tag was fixed, but that requires more code. This is good enough for me.

Another use for this function is excerpts. An original article may have all the tags properly closed but the excerpt might have an opening tag with it’s matching tag cut off.

A copy of the function is here.

It is not a validator. It ignores tags like p and br which will work even if not coded properly. You can edit the function to change which tags are allowed.

I used the function to alter Virginia CURE’s website from the original cover-wp theme. For comparison, I found a couple of other websites which use cover-wp: NELA Lives, Hedge Futures, and Marco Luthe. I’m not familiar with these websites, I just looked for websites that use the cover-wp theme. Eventually, any of these websites might disappear, or any of them might change themes, including Virginia CURE. But as I’m writing this they’re good examples. Virginia CURE’s excepts have the paragraphs preserved, the others don’t. The cover-wp theme uses a customized get_the_excerpt function, which in turn calls the built-in php function striptags to remove all of the tags.

Here’s copy of functions.php which I altered from cover-wp. I renamed it functions.txt so it can be viewed. I also made a small change to get_the_excerpt.

If anyone who reads this knows of a better function then I’d be interested. I’m just an amateur hack. But for now, my function works better than anything I found.

I fixed a couple of problems since I first posted it.

Combining feeds

A friend suggested I combine my schizophrenic group of blogs into a single blog. I’m not ready to do that but I did decide to display a feed from all my blogs on a single page. Easy enough, but I wanted to combine them all and sort them by date, rather than have a separate feed for each. I found a simple feed puller in a book called Plug-In PHP, by Robin Nixon. From that I modified and expanded the code to allow combining different feeds into one. Also, the books code worked for my wordpress blogs, but then I also wanted to make another feed which included blogs that I read, and my daughter users blogger. I had to write new code to accept her feed. I don’t know if this takes all kinds of feeds, but it does what I’ve needed it to do so far.

<?php
//Sort posts by publication date
function sortpdate ($a, $b) {
 $adate=strtotime($a[‘pubdate’]);
 $bdate=strtotime($b[‘pubdate’]);
 if ($adate == $bdate) { return 0;}
 return ($adate < $bdate) ? 1 : -1;
}

function RSStoHTML ($urllist, $usortfunction, $fullposts=3, $maxposts=12, $titletag=”h4″, $classprefix=””) {
//Takes blog posts from multiple blogs using a comma separated list of feeds, usorts them, and echos them
 

 $urls=explode(‘,’, $urllist);
 $i=0;

 $showitems=array();

 foreach ($urls AS $url) {
  
  $rss=file_get_contents($url);
  $xml = simplexml_load_string($rss);

  //The two foreach loops that follow pickup wordpress and blogger
  //I don’t know yet if these fail on other types of feeds.
  foreach($xml->channel->item as $item) {
   $i++;
   $showitems[$i][‘blogtitle’]=@$xml->channel->title;
   $showitems[$i][‘bloglink’]=@$xml->channel->link;
   $showitems[$i][‘blogdescription’]=@$xml->channel->description;
   $showitems[$i][‘itemlink’] = @$item->link;
   $showitems[$i][‘pubdate’] = @$item->pubDate;
   $showitems[$i][‘itemtitle’] = @$item->title;
   $showitems[$i][‘summary’] = @$item->description;
   $showitems[$i][‘content’] = @$item->children(“content”, true)->encoded;
  }
  foreach($xml->entry as $item) {
   $i++;
   $showitems[$i][‘blogtitle’]=@$xml->title;
   foreach (@$xml->children()->link AS $child ) {
    if($child->attributes()->rel==’alternate’) {
     $showitems[$i][‘bloglink’]=$child->attributes()->href;
    }
   }
   $showitems[$i][‘blogdescription’]=@$xml->subtitle;
   foreach (@$item->children()->link AS $child ) {
    if($child->attributes()->rel==’alternate’) {
     $showitems[$i][‘itemlink’]=$child->attributes()->href;
    }
   }
   $showitems[$i][‘pubdate’] = @$item->published;
   $showitems[$i][‘itemtitle’] = @$item->title;
   $showitems[$i][‘summary’] = substr ( @$item->content , 0, 30 );
   $showitems[$i][‘content’] = @$item->content;
  }
 }
 usort($showitems, $usortfunction);
 $out=””;
 $i=0;
 while ($i < sizeof($showitems) && $i < $maxposts) {
  if($i<$fullposts) {
   echo ‘<‘, $titletag, ‘ class=”‘, $classprefix, ‘itemtitle”><a href=”‘, $showitems[$i][‘itemlink’], ‘”>’, $showitems[$i][‘itemtitle’], ‘</a></’, $titletag, ‘><p class=”‘, $classprefix, ‘pubdate”>’, (date(‘l, Y F d’, strtotime($showitems[$i][‘pubdate’]))), ‘ in <a href=”‘, $showitems[$i][‘bloglink’], ‘”>’, $showitems[$i][‘blogtitle’]>”” ? $showitems[$i][‘blogtitle’] : $showitems[$i][‘bloglink’], ‘</a></p><p class=”‘, $classprefix, ‘summary”>’, $showitems[$i][‘content’], ‘</p>’;
  } else {
   echo ‘<‘, $titletag, ‘ class=”‘, $classprefix, ‘itemtitle”><a href=”‘, $showitems[$i][‘itemlink’], ‘”>’, $showitems[$i][‘itemtitle’], ‘</a></’, $titletag, ‘><p class=”‘, $classprefix, ‘pubdate”>’, (date(‘l, Y F d’, strtotime($showitems[$i][‘pubdate’]))), ‘ in <a href=”‘, $showitems[$i][‘bloglink’], ‘”>’, $showitems[$i][‘blogtitle’]>”” ? $showitems[$i][‘blogtitle’] : $showitems[$i][‘bloglink’], ‘</a></p><p class=”‘, $classprefix, ‘summary”>’, $showitems[$i][‘summary’], ‘</p>’;
  }
  $i++;
 }

 echo $out;
}

$urls=”https://bnmng.wordpress.com/feed,http://bnmngfarming.wordpress.com/feed,http://bnmngcomputing.wordpress.com/feed,http://healthyinsuffolk.wordpress.com,http://sashabyproxy.wordpress.com&#8221;;
RSStoHTML ($urls, ‘sortpdate’);