0

MODELS.PY

class Campaign(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    campaign_image = models.ImageField(default="profilepic.jpg",upload_to="campaign_pictures")

FORMS.PY

class RaiseFundsFrom3(forms.ModelForm):
    class Meta:
        model = Campaign
        fields = ['campaign_image']

VIEWS.PY

@login_required
def raise_funds_medical_3(request):
    if request.method == 'POST':
        form = RaiseFundsFrom3(request.POST, request.FILES or None, instance=request.user)
        if form.is_valid():
            check = form.save(commit=False)
            check.save()
            return HttpResponse('form worked')
    else:
        form = RaiseFundsFrom3()
        return render(request,'funds/raise_funds_medical_3.html',{'form':form})

URLS.PY

path('raise/medical/photo', views.raise_funds_medical_3, name="raise_funds_medical_3"),

raise_funds_medical_3.html

<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  <div class="form-group pt-2">
    <small>Photo formats must be PNG / JPG / JPEG</small>
    <input type="file" name="campaign_image" />
  </div>
  <button class="btn btn-lg button_bfg_blue" type="submit"> <small><b> NEXT  </b></small> </button>
</form>

on form submit, i do not get any error, but image is not uploaded to the required folder. however, in the raise_funds_medical_3 function within views.py, if i remove instance=request.user, the image gets uploaded but i get following error : NOT NULL constraint failed: funds_campaign.user_id

Irfan Harun
  • 979
  • 2
  • 16
  • 37
  • what's your MEDIA variable value in settings.py? – Artem Strogin Jan 09 '20 at 12:02
  • MEDIA_ROOT = os.path.join(BASE_DIR,'pictures') MEDIA_URL = '/pictures/' – Irfan Harun Jan 09 '20 at 12:06
  • You get "form worked" text printed in your browser? Your view does not handle the case that the form is not valid (it returns None in that case). – dirkgroten Jan 09 '20 at 12:10
  • Also, `instance=request.user` is **wrong**, the form is a model form for `Campaign` not for `User` so its instance cannot be `request.user`. Assign the user **after** saving the form with commit=False: `check = form.save(commit=False); check.user = request.user; check.save()`. – dirkgroten Jan 09 '20 at 12:12
  • @dirkgroten : the image got saved after i used check.user = request.user. Can you explain the flaw in my code ? – Irfan Harun Jan 09 '20 at 12:15

2 Answers2

3

Your form is a ModelForm for a Campaign, so its instance needs to be a Campaign. Don't assign request.user as its instance!

Now, your form isn't including the user field which is required to save a Campaign, so you should assign that yourself in the view before saving to the database:

campaign = form.save(commit=False)  # this gives your the form's instance
campaign.user = request.user  # this assigns the user
campaign.save()  # this commits to the database

Also you should handle the case where the form isn't valid. This is quite simple, just un-indent the last return in your view function, so that return render(...) is also called in case the form isn't valid.

Finally, instead of returning a response when the form is valid, it's good practice to redirect to another view. This way, when the user refreshes the page, the form isn't submitted again. Your final code should look like this:

@login_required
def raise_funds_medical_3(request):
    if request.method == 'POST':
        form = RaiseFundsFrom3(request.POST, request.FILES or None)
        if form.is_valid():
            check = form.save(commit=False)
            check.user = request.user
            check.save()
            return redirect(<url_pattern>)
    else:
        form = RaiseFundsFrom3()
    return render(request,'funds/raise_funds_medical_3.html',{'form':form})
dirkgroten
  • 20,112
  • 2
  • 29
  • 42
  • it all makes after reading your answer. Thank you so much. how can i get the uploaded file name or file url ?? – Irfan Harun Jan 09 '20 at 12:18
  • if you have an instance `campaign` then `campaign.campaign_image.url` is the full url to the image which you can use in the `src` attribute of `` – dirkgroten Jan 09 '20 at 12:20
0

Supplementary answer to dirkgroten's one

I have come to completely hate the conventional structuring of a Django Function-based View. They can be re-factored by inverting the validity test and adding one line so that one and only one instantiation of a form is present. The result is IMO far easier to read, and easily generalizes for a view displaying two or more forms.

def raise_funds_medical_3(request):
    args = [request.POST, request.FILES or None] if request.method == "POST" else []
    form = RaiseFundsFrom3(*args)
    if request.method != "POST" or not form.is_valid():
        # unbound form or form not valid
        return render(request,'funds/raise_funds_medical_3.html',{'form':form})

    # form is valid so do the processing and redirect
    check = form.save(commit=False)
    check.user = request.user
    check.save()
    return redirect(<url_pattern>)

If you want to process >1 form, the test becomes

    if request.method != "POST" or any(
        [ not form.is_valid(), not form2.is_valid(), ...]):

which forces evaluation of .is_valid() for all forms, even if the first was not valid, so that all the error messages are shown to the user.

In a complex business application, the processing of a successful form submission may be quite a few more lines of code than this simple example. Having it at the end, not indented, isolated from all the boilerplate save the return redirect(...), makes things much easier!

nigel222
  • 7,582
  • 1
  • 14
  • 22