Search code examples
djangodjango-modelsdjango-adminmany-to-many

How can I use model relationships to associate my preorder model with my customer model in the admin without creating new objects each time


How can I associate a customer with a preorder object, while not creating a bunch of objects for that model? When the ManyToMany relationship is used in the admin change page, rather than allowing me to associate multiple customers with one Preorder, when I try to associate a customer with an instance of the Preorder model, it seems to create a new object, instead of updating the current Preorder instance to include a new customer to associate with it. I want something like this:

(customer: John, id=3) is associated with (Preorder: NewItem, id=1), and (customer: Kim, id=5) is also associated with (Preorder: NewItem, id=1). So John and Kim have both preordered NewItem where id=1, and if I were to select Preorder:NewItem, id=1 from a table, it would show that John and Kim have preorderd this item

I basically want to show a list that displays all customers that have preordered a specific product. Instead, at least with the ManyToMany field, when I click add next Many field display, it prompts me to create a new Preorder to associate with that customer, instead of selecting an already existing one. This results in each customer having the same preorder names associated, but these preorders will have different pk on the db table. I have a Customer and Preorder model:

class Preorder(models.Model):
    product = models.CharField(max_length=100, default='')
    price = models.DecimalField(max_digits=12, decimal_places=2, default=None)
    quantity = models.IntegerField(default=1, )

    def __str__(self):
        return self.product


class Customer(models.Model):
    preorders = models.ManyToManyField(Preorder, blank=True, default=None)
    name = models.CharField(max_length=100, default='')
    email = models.EmailField(max_length=200, default='')
    credit = models.DecimalField(max_digits=12, decimal_places=2, default=None, null=True, blank=True, verbose_name='Store Credit')

    def __str__(self):
        return self.name

Solution

  • Number of ways you could approach this, but it feels like you have your ManyToMany field the wrong way around:

    class Preorder(models.Model):
        product = models.CharField(max_length=100, default='')
        price = models.DecimalField(max_digits=12, decimal_places=2, default=None)
        quantity = models.IntegerField(default=1, )
        customer = models.ManyToManyField(Customer, blank=True, default=None)
    
        def __str__(self):
            return self.product
    

    That way, you have one pre-order with two customers (John and Kim) - so you're not duplicating your pre-order objects, or your user objects!

    Then, in views.py:

    def preorders(request, product):
    
        context = {}
    
        preorders = Preorder.object.filter(product=product)
    
        return ...
    

    And in urls.py something like this:

    from django.conf.urls import url
    
    urlpatterns = [
        ...
        url(r'^preorders/(?P<product>\d+)/$', views.preorders, name='customer_preorders_by_product')),
        ...
    ]
    

    And your template:

    {% for preorder in preorders %}
    <ul>
        preorder.product
        <li>{% for customer in preorder.customer.all %}{{ customer }}{% endfor %}</li>
    </ul>
    {% endfor %}
    

    Hope that helps?