How can I edit form instance before it will be save using Class-Based-Views?

I have a little problem with “copy” input informations in forms before it will be save using CreateView and other Class-Based-Views.

My model.py:

    from django.db import models
    from stock.models import Material, Dimensionmat, Typemat, Grademat, Tensil
    from clients_suppliers.models import Client

# Create your models here.

class NumberingofProduct(models.Model):
    client = models.ForeignKey(Klient, on_delete=models.SET_NULL, null=True)
    no_drowing_prod = models.CharField('Number drawing, max_length=30, unique=True, blank=True, null=True)
    no_prod_clients = models.CharField('Client number of product, max_length=30, unique=True, blank=True, null=True)
    no_prod_mine = models.CharField('Mine number of product', max_length=30, unique=True, blank=True, null=True)

    class Meta:
        abstract = True
        ordering = ['no_drowing_prod']

    
class NumberofProductatStock(NumberingofProduct):
    class Meta:
        ordering = ['no_drowing_prod']

class NumberofProduct(NumberingofProduct):
    numberofproductatstock = models.ForeignKey(NumberofProductatStock, on_delete=models.SET_NULL, null=True)

    class Meta:
        ordering = ['no_drowing_prod']

    
class BaseInfoProduct(models.Model):
    """ Class abstract"""
    client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True)
    numberofproductatstock = models.ForeignKey(NumberofProductatStock, on_delete=models.SET_NULL, null=True)
    numberofproduct = models.ForeignKey(NumberofProduct, on_delete=models.SET_NULL, null=True)
    name_spr = models.CharField(max_length=40, blank=True, null=True)
    material = models.ForeignKey(Material, on_delete=models.SET_NULL, null=True)
    dimensiomat = models.ForeignKey(Dimensionmat, on_delete=models.SET_NULL, null=True)
    typemat = models.ForeignKey(Typemat, on_delete=models.SET_NULL, null=True)
    gradekmat = models.ForeignKey(Grademat, on_delete=models.SET_NULL, null=True)
    tensil = models.ForeignKey(Tensil, on_delete=models.SET_NULL, null=True)
    weight = models.DecimalField(max_digits=9, decimal_places=3, blank=True, null=True, help_text='Unit: grams')

    
    
class Springcompression(BaseInfoProduct):
    RIGHT = right
    LEFT = left
    DISCRETIONARY = 'discretionary'

    STATUS = [
        (RIGHT, 'Right'),
        (LEFT, 'Left'),
        (DISCRETIONARY, 'DISCRETIONARY '),
        ]

    kierunek_zwijania = models.TextField(max_length=7, choices=STATUS, default=RIGHT)
    dz = models.PositiveSmallIntegerField('Dz', blank=True, null=True)
    dz_tol_plus = models.DecimalField('Dz+', max_digits=4, decimal_places=2, blank=True, null=True)
    dz_tol_minus = models.DecimalField('Dz-', max_digits=4, decimal_places=2, blank=True, null=True)
    dp = models.PositiveSmallIntegerField('Dp', blank=True, null=True)
    dp_tol_plus = models.DecimalField('Dp+', max_digits=4, decimal_places=2, blank=True, null=True)
    dp_tol_minus = models.DecimalField('Dp-', max_digits=4, decimal_places=2, blank=True, null=True)
    dw = models.PositiveSmallIntegerField('Dw', blank=True, null=True)
    dw_tol_plus = models.DecimalField('Dw+', max_digits=4, decimal_places=2, blank=True, null=True)
    dw_tol_minus = models.DecimalField('Dw-', max_digits=4, decimal_places=2, blank=True, null=True)
    lo = models.PositiveSmallIntegerField('L0', blank=True, null=True)
    lo_tol_plus = models.DecimalField('L0+', max_digits=4, decimal_places=2, blank=True, null=True)
    lo_tol_minus = models.DecimalField('L0-', max_digits=4, decimal_places=2, blank=True, null=True)

forms.py

class SpringcompressionForm(ModelForm):
    class Meta:
        model = Springcompression
        exclude = ['numberofproduct']

    def __init__(self,*args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['dimensiomat'].queryset = Dimensionmat.objects.none()
        self.fields['Typemat'].queryset = Typemat.objects.none()
        self.fields['Grademat'].queryset = Grademat.objects.none()

views.py

class SpringcompressionListView(ListView):
    model = Springcompression
    context_object_name = 'springcompressions'


class SpringcompressionCreateView(CreateView):
    model = Springcompression
    form_class = SpringcompressionForm
    success_url = reverse_lazy('springcompression_changelist')


class SpringcompressionUpdateView(UpdateView):
    model = Springcompression
    form_class = SpringcompressionForm
    success_url = reverse_lazy('springcompression_changelist')

Please focus at two atrributes from BaseInfoProduct: numberofproductatstock and numberofproduct.

Lets assume that I would like to create my product first time ever for new client Coca-cola. It is the same numberofproduct as numberofproductatstock. So I do not want to burdon the user to write the same numbers into numberofproduct. I would like to write one view for automatically copy this numbers from numberofproductatstock into numberofproduct. And I do not show at this view field: numberofproduct.

Second example I assume that I could create the same product (the same numberofproductatstock) but with diffrent numberofproduct but for another client- Pepsi. The second view (and forms with all fields) should has both fields. At first numberofproductatstock to choose the number and then field for new numberofproduct for another client- Pepsi.

If I not use Class-Based-Views I think that I could write the function:

def copy_numbers_to_numberofproduct (request):
    form_product = SpringcompressionForm(request.POST or None)

    if form_product.is_valid():
        product = form_product.save(commit=False)
        product.numberofproduct.no_drawing_prod = product.numberofproductatstoct.no_drawing_prod
        product.numberofproduct.no_prod_clients = product.numberofproductatstock.no_prod_clients
        product.numberofproduct.no_prod_mine = product.numberofproductatstock.no_prod_mine
        product.save()
        return redirect('springcompression_changelist')
    
    context = {'form': form_product}
    return render(request, 'springcompression_form.html', context)

Please tell me would it be a good function? and what I should to do while I am using Class-Based-Views?

Are you using your own Class-Based Views? Or are you using one of the Django-provided generic CBVs?

If the latter, getting an understanding of how the generic CBVs are put together really helps figure out where to add functionality.

I start by recommending the Classy Class-Based View web site along with the CBV Diagrams page.

For example, you’ve added code to the true condition of the is_valid test. In the generic CBVs, that true condition calls the form_valid function of the class. So the code you’re looking to execute can be add to that function.

Thanks for your help!
I am using ListView and CreateView, nothing more. I am not sure if it is aswer for your question:/

I have checked your links and I wonder if the better idea would be inplement my “coping process” in here:

def form_valid(self, form):
    """If the form is valid, save the associated model."""
       
    MY CODE

    self.object = form.save()
    return super().form_valid(form)

but I dont know how I should catch the form and copy my number :frowning:
I do not what is packed into “self” and “form” because that function gets it:((((

How can I be sure that I call right function if they names are the same but from different classes (ModelFormMixin and FormMixin)?

Yes. Those are the “Django generic CBVs” (or “system” or “default” CBVs).
Those aren’t the only possible set of CBVs available to you. You could completely create your own, or use a 3rd party package such as the Django Vanilla Views. Your project could be using any combination of them. That’s why I needed the clarification.

That’s where the Classy CBV docs really shine. If you’re looking at a view, you can click on the individual function names and see what’s happening at each step of the way.
example:

  • Go to https://ccbv.co.uk/
  • Select UpdateView in the Generic Edit category
  • Scroll down to the def form_valid(... line and click on it.
    • It opens up showing two lines, ModelFormMixin and FormMixin
    • This is the order in which they are called.
  • Click on ModelFormMixin, and you will see the function itself.

You can follow this sequence from start to finish. If you remember, when you specify a Django generic CBV in your urls.py file, you actually specify the as_view method as being the view. That’s your starting point. But, you can save some time and effort by realizing that the real work begins with either the get or post functions. The setup work prior to that is important to understand, but it’s pretty much the same across the board.

Actually, both get called.

This topic is a general Python topic related to class inheritance. It’s referred to as the Method Resolution Order (MRO). The details are provided in the official Python docs. (There are also many other blog posts and references that explain it.)

Briefly, when you call a method on a class, it looks first at the current class (referenced by self), then does a depth-first search through the parent classes.
If the method does not call super(), the function call ends at that point.
If that method does call super(), then the next instance of that method is called.

The Django generic CBVs layer functionality by having each parent class do something and then call super to continue, until all necessary functions have been called.

Bottom line is, in most cases, you add your desired functionality to one of the existing functions by overriding it, and then call super.

The one exception to that that I encounter most often is when overriding form_valid to do the type of thing you’re looking to do. (Modify the object being saved by a form.)

It’s not unusual for us to have a form_valid method that looks like this:

def form_valid(self, form):
    self.object = form.save(commit=False)
    # Code modifying self.object goes here
    self.object.save()
    return HttpResponseRedirect(self.get_success_url())

Notice in this case we’re not calling super. There’s no need to call the corresponding form_valid methods of the parent classes.

1 Like

Thank you for your time and your help! The most helpful were diagrams.

I have done it what I what, in half of course hehe (now it is a problem with Updating)

I have called post function at views.py

class SpringcompressionCreateView(CreateView):
    model = Springcompression
    form_class = SpringcompressionForm
    success_url = reverse_lazy('springcompression_changelist')
def post(self, request, *args, **kwargs):
    """
    Handle POST requests: instantiate a form instance with the passed
    POST variables and then check if it's valid.
    """
    form = self.get_form()

    if form.is_valid():
        product = form.save(commit=False)
        product.numberofproduct.no_drawing_prod = product.numberofproductatstoct.no_drawing_prod
        product.numberofproduct.no_prod_clients = product.numberofproductatstock.no_prod_clients
        product.numberofproduct.no_prod_mine = product.numberofproductatstock.no_prod_mine
        product.save()
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

When I copied that into

class SpringcompressionUpdateView(UpdateView):
    model = Springcompression
    form_class = SpringcompressionForm
    success_url = reverse_lazy('springcompression_changelist')

I got the error : SpringcompressionUpdateView object has no attribute ‘object’

You do not want or need to override post. You want to override form_valid similar to my example above.

1 Like

I made a logic mistake. It is because I tested this solution at my origin language and program version (with one form).
The solution of course belong to Ken, but it does not fit to models with ForeignKeys!!!
Copy between two fields from one form is ok. Here I have a problem with combine few more forms. I should write Forms: NumberofProductForm, NumberofProductatStockForm
Once again the problem is with Class-Based-Views :frowning:
Normally I could use something like this for copy between two forms:

def new_springcompression(request):
form_springcompression = SpringcompressionForm(request.POST or None)
form_numberofproduct = Numberofproduct(request.POST or None)

if all(form_springcompression.is_valid(), form_numberofproduct.is_valid()):
    springcompression = form_springcompression.save(commit=False)
    numberofproduct = form_numberofproduct.save()
    springcompression.numberofproduct = numberofproduct
    springcompression.save()
    return redirect(...)

There is any way to solve my problem and replace above function def new_springcompression(request): something working with CBV?

The Django generic edit CBVs are not designed to work with multiple forms on the page.

If you ever have a situation where you’re handling multiple forms on the page, I wouldn’t recommend inheriting from anything other than View - and really, at that point, I’d pretty much end up recommending not using a Django CBV at all. Either build your own CBV if you’re going to have multiple cases like this, or just use an FBV.

The Django generic CBVs are good for a lot of things, but they’re not the last word for every situation.

I am agree with you. I read that article (Class-Based Views vs. Function-Based Views ) and get know where I am. I learn the whole DJANGO and I all the time I get something new :slight_smile:
But thanks of you I got know how Generic CBV works and how it looks like at diagrams :stuck_out_tongue:
Have you got any usefull website like