Costantino Grana
Costantino Grana

Reputation: 3418

Dual behavior for Class Based View

Let's say that we model university programs, which are divided in tracks. A track belongs to a program. So:

class Program(models.Model):
    name = models.CharField(max_length=30)

class Track(models.Model):
    name = models.CharField(max_length=30)
    program = models.ForeignKey(Program, on_delete=models.CASCADE, related_name='tracks')

I'd like to allow the creation of a track, with the possibility of specifying the program it belongs to, so I can use:

class TrackCreateView(CreateView):
    model = Track
    fields = ['name', 'program']

But from the program details, I want to allow the creation of a track for that specific program, so I came up with:

class ProgramTrackCreateView(CreateView):
    model = Track
    fields = ['name']

    def form_valid(self, form):
        form.instance.program_id = self.kwargs['pk']
        return super().form_valid(form)

In the urls.py, a pk parameter is provided, so this will populate the program_id correctly.

Is there any way to merge these two views into a single one, e.g. passing a parameter to the .to_view() method, or something similar? Because I need to change the list of fields to be displayed before the form is created and I need to know if I need to override program_id or not.

Upvotes: 1

Views: 58

Answers (3)

Abdul Aziz Barkat
Abdul Aziz Barkat

Reputation: 21787

One way would be to override your views get_form method and dynamically remove your forms field in case the pk is passed to the view:

class ProgramTrackCreateView(CreateView):
    model = Track
    fields = ['name', 'program']
    
    def get_form(self, form_class=None):
        form = super().get_form(form_class=form_class)
        program_id = self.kwargs.get('pk')
        if program_id is not None:
            del form.fields['program']
            form.instance.program_id = program_id
        return form

Upvotes: 1

Arjun Shahi
Arjun Shahi

Reputation: 7330

You can make use of the django forms and override the form's __init__ method.

from django import forms

class TaskCreateForm(forms.ModelForm):
    class Meta:
       fields = ['name', 'program']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if kwargs.get('pk'):
            form.fields['program'].widget = forms.HiddenInput()

Now in your view:

class ProgramTrackCreateView(CreateView):
    model = Track
    form_class = TaskCreateForm
    success_url = reverse_lazy('url')

    def form_valid(self, form):
        if self.kwargs.get('pk):
           form.instance.program_id = self.kwargs['pk']
        form.save()
        return super().form_valid(form)

Upvotes: 1

Henty
Henty

Reputation: 603

Make a FormView for this, in the init function of it you can then check against whatever context you decide to use.

def MyFormView(forms.Form):
    fields_for_all_options = forms.CharField(label='SomeLabel', max_length=200)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if case x:
            self.fields['somefield'] = forms.CharField(label= 'a different label', max_length=200)

Alternatively you could also pass all fields available on the form then hide them in the frontend

Upvotes: 0

Related Questions