Search code examples
djangoformsmany-to-manymodels

Django: ManyToManyField, add object if it doesn't already exist


I'm trying to use ModelForms in Django to add content to my database which includes a ManyToManyField. Here is the relevant part of my model:

class Category(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()

    def __unicode__(self):
        return self.name

class Recipe(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    user = models.ForeignKey(User)
    image = models.ImageField(upload_to='images/recipethumbs/', null = True, blank=True)
    ingredients = models.ManyToManyField(Ingredient, through="IngredientMap")
    categories = models.ManyToManyField(Category)
    citation = models.CharField(max_length=200)

    def __unicode__(self):

By default, the ManyToManyFields are represented by a multiple choice selection. This allows users to select from preexisting fields in the category object. I want to change this so that users can either select categories if they currently exist, or add new ones if they don't. This, seemingly, would be done by changing the widget to a TextInput.

class RecipeForm(ModelForm):
    class Meta:
        model = Recipe
        widgets = {
            'categories': TextInput(attrs={'size': 40,}),
            'ingredients': Textarea,
        }

But if a user enters a category that doesn't currently exist in the Categories table, then Django complains that I need to "Enter a List of Values". How can I get the form to add a new category if it doesn't already exist?

My solution in a custom .save() as Ilvar suggested:

categories = re.findall(r'\w+[\w\s]+', self.cleaned_data.get('categories')) #returns an array

        if commit:
            m.save()

        # You can only assign m2m if the Recipe object has been saved.
        for category in categories:
            try:
                category_in_db = Category.objects.get(name=category)
            except:
                category_in_db = None
            if category_in_db:
                m.categories.add(category_in_db)
            else:
                m.categories.create(name=category)

        return m

Solution

  • You need to exclude m2m field from your form and add a CharField to form class itself manually. Then override __init__ to set this field in initial and save. In save you can do whatever you want to the text in cleaned_data. I guess, in your case it will be splitting it by commas, getting_or_creating a category for each value, and assigning category list to the field in object after saving parent form an before returning the result. Maybe also add some autocomplete to avoid mistyping on clientside.