Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

passing args and kwargs to parent class with extra content in django CreateView

I've made a django form in which I want to pass some context I need to display (mainly database entries in tags so the user can choose from them). I therefor made a get_context_data function where I add the context to the existing context like this:

def get_context_data(self, **kwargs):
    context = super(UploadView, self).get_context_data(**kwargs)
    context['categories'] = Category.objects.all()
    context['form'] = VideoForm
    return context

however, the form is not saving the information passed to the database. Why would that not work?

Here is part of my code!

forms.py:

class VideoForm(forms.ModelForm):
    category = forms.ModelChoiceField(queryset=Category.objects.all(), empty_label=None)
    class Meta:
        model = Video
        fields = [
            'title',
            'description',
            'description_short',
            'category',
            'time',
            'thumbnail',
            'type',
        ]

    def clean_thumbnail(self):
        picture = self.cleaned_data.get("thumbnail")
        if not picture:
            raise forms.ValidationError("No Image")
        else:
            w, h = get_image_dimensions(picture)
            if w/h != (16/9):
                raise forms.ValidationError("Image in wrong aspect ratio (should be 16:9)")
        return picture

upload.html (it's pretty long so it's better to upload it to Pastebin)

views.py:

class UploadView(LoginRequiredMixin, CreateView):
   form_class = VideoForm
   template_name= 'upload.html'

   def form_valid(self, form):
       instance = form.save(commit=False)
       instance.uploader=self.request.user
       return super(UploadView, self).form_valid(form)

   def get_context_data(self, **kwargs):
       context = super(UploadView, self).get_context_data(**kwargs)
       context['categories'] = Category.objects.all()
       context['form'] = VideoForm
       return context

I'm using a custom form so I can set classes which I use for editing the CSS (instead of just using form.as_p and having a really ugly from there...)

EDIT:

After a bit more testing I found that if I put print(instance) inside the def form_valid(self, form): function it would not print out anything, suggesting the instance is empty. How can this be? Also, I tried removing: context['form'] = VideoForm and no errors show up but if I fill in the form correctly it still doesn't work!

EDIT 2:

I created a 'form_invalid' function like this:

def form_invalid(self, form):
    print(form.instance)
    return super(UploadView, self).form_invalid()

which causes:

TypeError: form_invalid() missing 1 required positional argument: 'form'

This had me thinking as I am not receiving any errors when I get redirected back to the form after submitting. Here is the form I wrote: https://pastebin.com/3H6VRZR1

Also I tried testing it with just form.as_p which causes the same problem

EDIT 3:

After testing some of the answers we found out that the form of the HTML page itself is probably the cause as the form arrived in form_invalid() completely empty. I decided that I would try to use form.as_p again see if it still caused the same issue as my custom form. Now I'm getting a new error:

null value in column "category_id" violates not-null constraint
DETAIL:  Failing row contains (8, test new fom reg, test-new-fom-reg, null, test, test, 2018-01-10 13:44:58.81876+00, 2018-01-10 13:44:58.818789+00, 1, thumbnails/test-new-fom-reg.png, 2, 1, /home/trie/Desktop/django/vidmiotest/media/videos/test.mp4).

with:

USER
admin

GET
No GET data

POST
Variable Value
title 'test new fom reg'
category '8'
type '1'
time '1'
description 'test'
csrfmiddlewaretoken `BeizxWHU5KDbixit9vpxKoxEeBxgU9MNITaNlkM1qtI0Aq6kIThHrtjfUsQXjxON'
description_short 'test'

FILES
Variable Value
thumbnail <TemporaryUploadedFile: sixteentonineratio.png (image/jpeg)>
videoFile <TemporaryUploadedFile: test 3.mp4 (video/mp4)>

as the data the data send from the form which suggest (based on this) that category_id is not in my model for the form. which it is (the field is just called category), but why would it think it should be in there?

I just checked phppgadmin to see what the database looked like and there the field is called id_category while in my model it's called category, why?

EDIT 4: I never added the Traceback for the error above:

Internal Server Error: /upload/ Traceback (most recent call last): File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 85, in _execute return self.cursor.execute(sql, params) psycopg2.IntegrityError: null value in column "category_id" violates not-null constraint DETAIL: Failing row contains (12, test, test, null, test, test, 2018-01-16 18:18:25.907513+00, 2018-01-16 18:18:25.907538+00, 6, thumbnails/test_d1MHjMX.png, 2, 1, /home/trie/Desktop/django/vidmiotest/media/videos/test.mp4).

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 35, in inner
    response = get_response(request)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 128, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 126, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/views/generic/base.py", line 69, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/contrib/auth/mixins.py", line 52, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/views/generic/base.py", line 89, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/views/generic/edit.py", line 172, in post
    return super().post(request, *args, **kwargs)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/views/generic/edit.py", line 142, in post
    return self.form_valid(form)
  File "/home/trie/Desktop/django/vidmiotest/upload/views.py", line 21, in form_valid
    instance.save()
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/base.py", line 729, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/base.py", line 759, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/base.py", line 842, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/base.py", line 880, in _do_insert
    using=using, raw=raw)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/query.py", line 1125, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 1280, in execute_sql
    cursor.execute(sql, params)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 100, in execute
    return super().execute(sql, params)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/trie/Desktop/django/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: null value in column "category_id" violates not-null constraint
DETAIL:  Failing row contains (12, test, test, null, test, test, 2018-01-16 18:18:25.907513+00, 2018-01-16 18:18:25.907538+00, 6, thumbnails/test_d1MHjMX.png, 2, 1, /home/trie/Desktop/django/vidmiotest/media/videos/test.mp4).
like image 972
BRHSM Avatar asked Jan 07 '18 13:01

BRHSM


1 Answers

The problem in the question is that super().form_valid() is called and overwriting the constructed form with context['form'] = VideoForm. Let the framework setup the form when using CreateView.

The inherited CreateView provides the functionality to setup the form, save the form, set self.object on the view, and redirect to a success url.

I.e CreateView.form_valid provides:

self.object = form.save()

The solution is correct to set the uploader in UploadView, but calling the super.form_valid will try to save the form again.

As I understand the desired behaviour is:

  1. set uploader
  2. save the object
  3. redirect to success url

in code:

instance = form.save(commit=False)
instance.uploader = self.request.user
instance.save()
return redirect(self.get_success_url()) # Skip the call to super

Also as pointed out in other answer, the context['form'] = VideoForm will overwrite the form setup by the CreateView.

I suggest to look how the execution flow works for the CreateView, it will setup the form for the template context both in GET and POST situation.

One other way to solve it is to allow the VideoForm to accept uploader in init:

class VideoForm(forms.ModelForm):
    def __init__(self, uploader, *args, **kwargs):
        self.uploader = uploader
        super().__init__(*args, **kwargs)

    def save(self, commit=True):
        instance = super().save(commit=False)
        instance.uploader = self.uploader
        if commit:
           instance.save()
        return instance

and provide the uploader to the form

 class UploadView(..., CreateView):
     def get_form_kwargs(self):
         kwargs= super().get_form_kwargs()
         kwargs['uploader'] = self.request.user
         return kwargs

Hope it helps.

Looking at EDIT 3:

The reason it's say 'category_id' is that foreign keys in a django model will automatically get suffixed with '_id' as column name in the database. Documentation.

The Video model is likely to have a foreign key to Category.

If you find id_category in database, you might have gone out of sync with your migrations/models? You should probably try clear the database and/or to re-run makemigrations/migrate

like image 90
Daniel Backman Avatar answered Sep 18 '22 04:09

Daniel Backman