Search code examples
pythondjangoformsdjango-crispy-formsmanytomanyfield

How to edit/update a model via Django form with manytomany field?


I am facing an issue while editing/updating a form with a manytomany field. I dont know how to implement the logic for saving a form with a manytomany field. The model Sam is an user, who manages different accounts and over a period of time the accounts he manages change. So there should be flexibility in adding or removing the accounts he manages via the Samprofileupdateform which includes a manytomany field for Account. Can you please help me how to implement this?

models.py

class Account(models.Model):
   accnt_nagp = models.CharField(max_length=30, unique=True, primary_key=True)
   #sam_name = models.ManyToManyField(Sam)
   def __unicode__(self):
     return  self.accnt_nagp

class Sam(models.Model):
    SUNNYVALE = 'SVL'
    NORTHCAROLINA = 'RTP'
    EUROPE = 'EMEA'
    INDIA = 'NB'
    AUSTRALIA = 'AUS'
    suppaccntmgr = 'SAM'
    MANAGER = 'SAM_MGR'
    REGION_CHOICES = (
       (SUNNYVALE, 'Sunnyvale'),
       (NORTHCAROLINA, 'RTP'),
       (EUROPE,'EMEA'),
       (INDIA,'NB'),
       (AUSTRALIA,'AUS'),
      )
    DESIGNATION_CHOICES = (
       (suppaccntmgr, 'SAM'),
       (MANAGER, 'SAM_MGR'),
      )
    user = models.OneToOneField(User)
    designation = models.CharField(max_length=8, choices=DESIGNATION_CHOICES, 
                        default=suppaccntmgr)
    mgr = models.ForeignKey(SamMgr)
    accnt = models.ManyToManyField(Account)

    def __unicode__(self):
        return u'%s' % self.user.username

Views.py :

class SamProfileUpdateView(views.LoginRequiredMixin, UpdateView):
    model = Sam
    form_class = SamProfileUpdateForm
    success_url = reverse_lazy('risklist')
    template_name = 'samrunbook/samaccntassociate.html'

forms.py

class SamProfileUpdateForm(forms.ModelForm):
    class Meta:
       model = Sam
       fields = ('accnt','mgr')
    def __init__(self, *args, **kwargs):
       super(SamProfileUpdateForm, self).__init__(*args, **kwargs)
       self.helper = FormHelper(self)
       self.helper.form_method = 'POST'
       self.helper.form_class = 'form-horizontal'
       self.helper.label_class = 'col-md-3'
       self.helper.field_class = 'col-md-6'
       self.helper.layout = Layout(
            'accnt',
            'mgr',
          FormActions(
                Submit('map', 'Map Your Account', css_class="btn-primary col-md-offset-3 col-md-6")
                )
        )

templates.py

{% extends 'samrunbook/base_risk.html' %}
{% load crispy_forms_tags %}

{% block content %}
<h3 class="col-md-offset-5">Login | Risk Register</h3>
{% csrf_token %}
{% crispy form %}
{% endblock %}

Solution

  • I have created this custom view function to create/update/edit a user profile managing multiple accounts/customers. I did this via Intermediate model to create the ManytoMany relationships between the two models Sam and Account. the intermediate model is SamAccount. Please find the below code for the models, view function and also form. The issue is resolved now.

    form AccountMapForm :

    class AccountMapForm(forms.Form):
        account_nagp = forms.CharField()
    
        def __init__(self, *args, **kwargs):
           super(AccountMapForm, self).__init__(*args, **kwargs)
           self.helper = FormHelper()
           self.helper.form_class = 'form-horizontal'
           self.helper.label_class = 'col-md-4'
           self.helper.field_class = 'col-md-6'
           self.helper.layout = Layout(
               Field('account_nagp', css_class='input-xlarge'),
               FormActions(
                  Submit('save_changes', 'Map', css_class='btn-primary col-md-offset-1 col-md-4'),
                  Submit('un_map', 'UnMap', css_class='btn-primary col-md-offset-1 col-md-4'),
                )
            )
    

    View Function:

    def account_map(request, slug):
       user_id = slug
       sam = Sam.objects.get(user_id=user_id)
       sam_check = SamAccount.objects.filter(sam=sam)
       message_account_map = "Please enter the Account Nagp you want to Map to your Profile"
       message_account_unmap = "Account is succesfully UnMapped"
       message_account_map_success = 1
       message_account_unmap_success = 1
       if 'save_changes' in request.POST:
           print "yes save_changes is there"
       if 'un_map' in request.POST:
           print "Yes Unmap is there"
       if request.method == 'POST':
           form = AccountMapForm(request.POST)
           if form.is_valid():
              user_id = slug
              account_from_form = form.cleaned_data['account_nagp']
              try:
                account = Account.objects.get(accnt_nagp=account_from_form)
                try:
                    if 'save_changes' in request.POST:
                        account = SamAccount.objects.get(sam=sam, account=account_from_form)
                        message_account_map = "Account is already Mapped"
                        message_account_map_success = 0 # To get the color change for the Warning
                    else:
                        SamAccount.objects.get(sam=sam, account=account_from_form).delete()
                        message_account_unmap_success = 0
                except ObjectDoesNotExist:
                    if 'save_changes' in request.POST:
                        SamAccount.objects.create(sam=sam, account=account)
                        message_account_map = "Account is succesfully Mapped to your profile"
                    else:
                        message_account_unmap = "The requested Account was not mapped to your profile"
                        message_account_unmap_success = 1
              except ObjectDoesNotExist:
                account = Account.objects.create(accnt_nagp=account_from_form)
                SamAccount.objects.create(sam=sam, account=account)
                message_account_map = "Created the Account and succesfully Mapped to your profile"
       else:
          form = AccountMapForm()
          template = loader.get_template('book/account_map.html')
          context = RequestContext(request, {
                'form' : form,
                'message_account_map' : message_account_map,
                'message_account_unmap' : message_account_unmap,
                'message_account_map_success' : message_account_map_success,
                'message_account_unmap_success' : message_account_unmap_success,
        })
       return HttpResponse(template.render(context))
    

    Model Account :

    class Account(models.Model):
       """Each Account Can have multiple SAMs"""
       accnt_nagp = models.CharField(max_length=30, unique=True, primary_key=True)
       sam_name = models.ManyToManyField('Sam', related_name='managed_by', through='SamAccount')
       def __unicode__(self):
    

    model Sam:

    class Sam(models.Model):
       """Sam can have Multiple Accounts"""
       SUNNYVALE = 'SVL'
       NORTHCAROLINA = 'RTP'
       EUROPE = 'EMEA'
       INDIA = 'NB'
       AUSTRALIA = 'AUS'
       ROLE = 'SAM'
       MANAGER = 'SAM_MGR'
       REGION_CHOICES = (
        (SUNNYVALE, 'Sunnyvale'),
        (NORTHCAROLINA, 'RTP'),
        (EUROPE,'EMEA'),
        (INDIA,'NB'),
        (AUSTRALIA,'AUS'),
        )
      DESIGNATION_CHOICES = (
        (ROLE1, 'SAM'),
        (MANAGER, 'SAM_MGR'),
        )
      user = models.OneToOneField(User)
      designation = models.CharField(max_length=8, choices=DESIGNATION_CHOICES,     default=ROLE)
      mgr = models.ForeignKey(SamMgr)
      slug = models.SlugField(max_length=255)
      accnt = models.ManyToManyField(Account, related_name='accounts_managed', through='SamAccount')
    
      def __unicode__(self):
          return u'%s' % self.user.username
    

    Intermediate Model to handle the ManyToMany relationship between the two models. if have to create a relation between the two models, all i have to do is create a object in the Intermediate model for the corresponding foreign key fields.

    Intermediate model SamAccount:

    class SamAccount(models.Model):
       account = models.ForeignKey('Account', on_delete=models.CASCADE)
       sam = models.ForeignKey('Sam', on_delete=models.CASCADE)
    
       def __unicode__(self):
           return u'%s' % self.account