Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 932541
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 15, 20262026-05-15T20:41:09+00:00 2026-05-15T20:41:09+00:00

I’ve been working through an ordered ManyToManyField widget, and have the front-end aspect of

  • 0

I’ve been working through an ordered ManyToManyField widget, and have the front-end aspect of it working nicely:

alt text

Unfortunately, I’m having a great deal of trouble getting the backend working. The obvious way to hook up the backend is to use a through table keyed off a model with ForeignKeys to both sides of the relationship and overwrite the save method. This would work great, except that due to idiosyncrasies of the content, it is an absolute requirement that this widget be placed in a fieldset (using the ModelAdmin fieldsets property), which is apparently not possible.

I’m out of ideas. Any suggestions?

Thanks!

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-15T20:41:09+00:00Added an answer on May 15, 2026 at 8:41 pm

    In regard to how to set up the models, you’re right in that a through table with an “order” column is the ideal way to represent it. You’re also right in that Django will not let you refer to that relationship in a fieldset. The trick to cracking this problem is to remember that the field names you specify in the “fieldsets” or “fields” of a ModelAdmin do not actually refer to the fields of the Model, but to the fields of the ModelForm, which we are free to override to our heart’s delight. With many2many fields, this gets tricky, but bear with me:

    Let’s say you’re trying to represent contests and competitors that compete in them, with an ordered many2many between contests and competitors where the order represents the competitors’ ranking in that contest. Your models.py would then look like this:

    from django.db import models
    
    class Contest(models.Model):
        name = models.CharField(max_length=50)
        # More fields here, if you like.
        contestants = models.ManyToManyField('Contestant', through='ContestResults')
    
    class Contestant(models.Model):
        name = models.CharField(max_length=50)
    
    class ContestResults(models.Model):
        contest = models.ForeignKey(Contest)
        contestant = models.ForeignKey(Contestant)
        rank = models.IntegerField()
    

    Hopefully, this is similar to what you’re dealing with. Now, for the admin. I’ve written an example admin.py with plenty of comments to explain what’s happening, but here’s a summary to help you along:

    Since I don’t have the code to the ordered m2m widget you’ve written, I’ve used a placeholder dummy widget that simply inherits from TextInput. The input holds a comma-separated list (without spaces) of contestant IDs, and the order of their appearance in the string determines the value of their “rank” column in the ContestResults model.

    What happens is that we override the default ModelForm for Contest with our own, and then define a “results” field inside it (we can’t call the field “contestants”, since there would be a name conflict with the m2m field in the model). We then override __init__(), which is called when the form is displayed in the admin, so we can fetch any ContestResults that may have already been defined for the Contest, and use them to populate the widget. We also override save(), so that we can in turn get the data from the widget and create the needed ContestResults.

    Note that for the sake of simplicity this example omits things like validation of the data from the widget, so things will break if you try to type in anything unexpected in the text input. Also, the code for creating the ContestResults is quite simplistic, and could be greatly improved upon.

    I should also add that I’ve actually ran this code and verified that it works.

    from django import forms
    from django.contrib import admin
    from models import Contest, Contestant, ContestResults
    
    # Generates a function that sequentially calls the two functions that were
    # passed to it
    def func_concat(old_func, new_func):
        def function():
            old_func()
            new_func()
        return function
    
    # A dummy widget to be replaced with your own.
    class OrderedManyToManyWidget(forms.widgets.TextInput):
        pass
    
    # A simple CharField that shows a comma-separated list of contestant IDs.
    class ResultsField(forms.CharField):
        widget = OrderedManyToManyWidget()
    
    class ContestAdminForm(forms.models.ModelForm):
        # Any fields declared here can be referred to in the "fieldsets" or
        # "fields" of the ModelAdmin. It is crucial that our custom field does not
        # use the same name as the m2m field field in the model ("contestants" in
        # our example).
        results = ResultsField()
    
        # Be sure to specify your model here.
        class Meta:
            model = Contest
    
        # Override init so we can populate the form field with the existing data.
        def __init__(self, *args, **kwargs):
            instance = kwargs.get('instance', None)
            # See if we are editing an existing Contest. If not, there is nothing
            # to be done.
            if instance and instance.pk:
                # Get a list of all the IDs of the contestants already specified
                # for this contest.
                contestants = ContestResults.objects.filter(contest=instance).order_by('rank').values_list('contestant_id', flat=True)
                # Make them into a comma-separated string, and put them in our
                # custom field.
                self.base_fields['results'].initial = ','.join(map(str, contestants))
                # Depending on how you've written your widget, you can pass things
                # like a list of available contestants to it here, if necessary.
            super(ContestAdminForm, self).__init__(*args, **kwargs)
    
        def save(self, *args, **kwargs):
            # This "commit" business complicates things somewhat. When true, it 
            # means that the model instance will actually be saved and all is
            # good. When false, save() returns an unsaved instance of the model.
            # When save() calls are made by the Django admin, commit is pretty
            # much invariably false, though I'm not sure why. This is a problem
            # because when creating a new Contest instance, it needs to have been
            # saved in the DB and have a PK, before we can create ContestResults.
            # Fortunately, all models have a built-in method called save_m2m()
            # which will always be executed after save(), and we can append our
            # ContestResults-creating code to the existing same_m2m() method.
            commit = kwargs.get('commit', True)
            # Save the Contest and get an instance of the saved model
            instance = super(ContestAdminForm, self).save(*args, **kwargs)
            # This is known as a lexical closure, which means that if we store
            # this function and execute it later on, it will execute in the same
            # context (i.e. it will have access to the current instance and self).
            def save_m2m():
                # This is really naive code and should be improved upon,
                # especially in terms of validation, but the basic gist is to make
                # the needed ContestResults. For now, we'll just delete any
                # existing ContestResults for this Contest and create them anew.
                ContestResults.objects.filter(contest=instance).delete()
                # Make a list of (rank, contestant ID) tuples from the comma-
                # -separated list of contestant IDs we get from the results field.
                formdata = enumerate(map(int, self.cleaned_data['results'].split(',')), 1)
                for rank, contestant in formdata:
                    ContestResults.objects.create(contest=instance, contestant_id=contestant, rank=rank)
            if commit:
                # If we're committing (fat chance), simply run the closure.
                save_m2m()
            else:
                # Using a function concatenator, ensure our save_m2m closure is
                # called after the existing save_m2m function (which will be
                # called later on if commit is False).
                self.save_m2m = func_concat(self.save_m2m, save_m2m)
            # Return the instance like a good save() method.
            return instance
    
    class ContestAdmin(admin.ModelAdmin):
        # The precious fieldsets.
        fieldsets = (
            ('Basic Info', {
                'fields': ('name', 'results',)
            }),)
        # Here's where we override our form
        form = ContestAdminForm
    
    admin.site.register(Contest, ContestAdmin)
    

    In case you’re wondering, I had ran into this problem myself on a project I’ve been working on, so most of this code comes from that project. I hope you find it useful.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I have a jquery bug and I've been looking for hours now, I can't
link Im having trouble converting the html entites into html characters, (&# 8217;) i
this is what i have right now Drawing an RSS feed into the php,
I have just tried to save a simple *.rtf file with some websites and
I'm looking for suggestions for debugging... If you view this site in Firefox or
Seemingly simple, but I cannot find anything relevant on the web. What is the
Does anyone know how can I replace this 2 symbol below from the string
I'm trying to decode HTML entries from here NYTimes.com and I cannot figure out
That's pretty much it. I'm using Nokogiri to scrape a web page what has
I want to count how many characters a certain string has in PHP, but

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.