Django - Update View, creating new Objects instead of updating, - python

I am trying to use Update view in django, in the simplest manner, but it is not being updated, rather a new object of the model is being created in the database. I have done the same thing for another model Track, and its working fine. I feel it might be something trivial that might be causing the problem.
I am modifying the PK of the model here. Could this be the reason?
View:
from django.views.generic.edit import UpdateView
from musictracker.models.datamodels.Genre import Genre
class EditGenre(UpdateView):
model = Genre
template_name = "editGenre.html"
fields = ['name']
Template:
{% extends 'base.html' %} {% load addcss %} {% block content %}
<div id="regContainer">
<ul>
<!-- {% if form.errors %} {{form.errors}} {% endif %} -->
</ul>
<div class="form-group">
<form method="post" action="">
{% csrf_token %}
{% for field in form %}
<label class="control-label" for="form-control input-sm">{{field.label_tag }}</label>
<br /> {{field|addcss:"form-control input-sm"}}
<br />
{% endfor %}
<input type="submit" id="register" value="Edit Genre" class="btn btn-default">
</form>
</div>
</div>
{% endblock %}
URLS.py
from django.conf.urls import url
from django.contrib import admin
''' View Imports '''
from views import TrackList
from views import AddTrack
from views import TrackDetail
from views import EditTrack
from views import GenreList
from views import GenreDetail
from views import AddGenre
from views import EditGenre
urlpatterns = [
url(r'^tracks/', TrackList.as_view(),name='all-tracks'),
url(r'^addTrack/', AddTrack.as_view(),name='add-tracks'),
url(r'^editTrack/(?P<pk>[0-9]+)', EditTrack.as_view(),name='edit-track'),
url(r'^track/(?P<pk>[0-9]+)', TrackDetail.as_view(),name='track'),
url(r'^genres/', GenreList.as_view(),name='all-genres'),
url(r'^addGenre/', AddGenre.as_view(),name='add-genre'),
url(r'^editGenre/(?P<pk>[a-zA-Z0-9]+)', EditGenre.as_view(),name='genre'),
url(r'^genre/(?P<pk>[a-zA-Z0-9]+)', GenreDetail.as_view(),name='genre'),
]
Model:
class Genre(models.Model):
'''
This is a model for Genres
'''
name = models.CharField(max_length=20,primary_key=True,editable=True)
songcount = models.IntegerField()
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse('genre', kwargs={'pk': self.pk})

Well, as other folks said in comments, your view creates new object instead of update because you have editable primary key.
You see, undercover UpdateView creates form for your model and calls save on that form.
It's the save method of BaseModelForm which operates self.instance attribute. And if instance isn't being found by pk, new one will be created. So I suggest you to recreate your model with uneditable primary key and leave name as just simple char field.

For future visitors - I experienced the same issue, but did not have an editable primary key. In my case I reused a form for both CreateView and UpdateView and forgot to allow for the difference in form action:
<form role="form" class="form-horizontal" action="{% url createobject' %}" method="post" multipart/form-data">
But it should have been
{% if not object %}
<form role="form" class="form-horizontal" action="{% url 'object:createobject' %}" method="post" multipart/form-data">
{% else %}
<form role="form" class="form-horizontal" action="{% url 'object:updateobject' object.pk %}" method="post" enctype="multipart/form-data">
{% endif %}
So, whenever I submitted the form, the action called my Createview instead of my UpdateView.

Related

How to just display one field from the form in Django

I want to display just one field from my form that I have created but I am getting 'Could not parse the remainder:' error
Here is my forms.py file
from django import forms
from .models import *
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ('prod_name', 'company', 'quantity', 'price', 'units', 'prod_type')
Here is my html file
{% extends 'base.html' %}
{% block body %}
<div class="container">
<form method="POST">
<br>
{% csrf_token %}
{% for field in form %}
{% if field.name=='units' %}
<div class ="form-form row">
<label for="id_{{field.name}}" class="col-2 col-form-label">{{field.label}}</label>
<div class ="col-10">
{{field}}
</div>
</div>
{% endif %}
{% endfor %}
<button type="submit" class="btn btn-primary" name="button">Update Sales</button>
</form>
</div>
{% endblock %}
I just want to display units in my webpage for this module that I am creating
I think you try to solve the problem on the wrong level. You can just construct a form with one field:
from django import forms
from .models import *
class UnitProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ('units',)
If you need the other form as well, you can just create an extra one, with a different name, like here UnitProductForm.
Using a form with a subset of fields is not only easier to render. The form will not make modifications to fields of a model object if these are not specified, even if these items are passed in a (forged) POST request. So it makes it more safe to use as well.
If you want to display only prod_name field then you can do it:
{
from django import forms
from .models import *
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ('prod_name')
}
Displaying just one field can be controlled from the templates like:
{% extends 'base.html' %}
{% block body %}
<div class="container">
<form method="POST">
{% csrf_token %}
{{ form.units.label_tag }} /*Shows the label for the input field */
{{ form.units }} /*Shows the input field */
<button type="submit" class="btn btn-primary" name="button">Update Sales</button>
</form>
</div>
{% endblock %}
This is from the official documentation, found here.
Note that you will have to handle creation of the object in the backend, if you choose to display only certain fields like this.

Upload image and show it on template - Django 2.0

I have this code on my template:
{% extends "base.html" %}
{% block content %}
<div style="padding:40px;margin:40px;border:1px solid #ccc">
<h1>picture</h1>
<form action="" method="post" enctype="multipart/form-data"> <!--{% url 'imageupload' %}-->
{% csrf_token %} {{form}}
<input type="submit" value="Upload" />
</form>
{% for img in images %}
{{forloop.counter}}.{{ img.pic.name }}
({{img.upload_date}})<hr />
{% endfor %}
</div>
{% endblock content %}
Although I have doubts with the form action but anyways.
The problem is that when I click on submit button, it takes me to a blank page, no pop-up to actually upload the image, nothing.
Is there some example I should follow to accomplish this?
Also, this is just a proof test, but I'd like to know if a model and/or form is actually needed for it to work?
EDIT
Okay, by editing the input line like this <input type="file" name='input_name' /> it actually opens the file pop-up, but obviously it doesn't upload anything, it needs like a submit button or something, so, now it looks like this:
{% extends "base.html" %}
{% block content %}
<div style="padding:40px;margin:40px;border:1px solid #ccc">
<h1>picture</h1>
<form action="" method="post" enctype="multipart/form-data"> <!--{% url 'imageupload' %} -->
{% csrf_token %} {{form}}
<input type="file" name='input_name' />
<input type="submit" value="Upload" />
</form>
{% for img in images %}
{{forloop.counter}}.{{ img.pic.name }}
({{img.upload_date}})<hr />
{% endfor %}
{% endblock content %}
But when I click on submit, it keeps sending me to a blank page, so, the value on submit, which is Upload, comes from this model:
from django.db import models
from django.forms import ModelForm
class Upload(models.Model):
pic = models.FileField(upload_to="static/")
upload_date=models.DateTimeField(auto_now_add =True)
class UploadForm(ModelForm):
class Meta:
model = Upload
fields = ('pic',)
And on my views.py:
from django.shortcuts import render
from uploader.models import UploadForm,Upload
from django.http import HttpResponseRedirect
from django.urls import reverse
def home(request):
if request.method=="POST":
img = UploadForm(request.POST, request.FILES)
if img.is_valid():
img.save()
return HttpResponseRedirect(reverse('imageupload'))
else:
img=UploadForm()
images=Upload.objects.all()
return render(request,'image_upload.html',{'form':img,'images':images})
And in my urls.py:
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib import admin
from django.views.generic import TemplateView
from django.views import defaults as default_views
from uploader import views as uploader_views
urlpatterns = [...
some patterns...
url(r'^image_upload/', uploader_views.home, name='imageupload'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Upload CSV file in django admin list view, replacing add object button

I want to replace the add object button in the listview of an admin page. The underlying idea is that an administrator can download data on all models in the db, use a tool to edit the data, and then reupload as a CSV file.
In the list view I am struggling to override the form, as setting
class SomeModelForm(forms.Form):
csv_file = forms.FileField(required=False, label="please select a file")
class Meta:
model = MyModel
fields = '__all__'
class SomeModel(admin.ModelAdmin):
change_list_template = 'admin/my_app/somemodel/change_list.html'
form = SomeModelForm
other stuff
The admin change_list.html is overridden as follows:
{% extends "admin/change_list.html" %}
{% load i18n admin_urls admin_static admin_list %}
{% block object-tools-items %}
<form action="{% url 'admin:custom_submit_row' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>
{{ form.as_p }}
</p>
<p><input type="submit" value="Upload" /><input type="reset" value="Reset"></p>
</form>
{% endblock %}
Previously SomeModel was missing the class Meta, as per sebbs response this is updated. The original error has been resolved but now currently the admin page is displaying the upload and reset buttons but no field for file uploads.
cheers
Edited with sebb's input below. Thanks sebb.
The error fixed was
< class ‘my_model.admin.SomeModelAdmin'>: (admin.E016) The value of 'form' must inherit from 'BaseModelForm'
OP here, solution is as follows:
class SomeModelForm(forms.Form):
csv_file = forms.FileField(required=False, label="please select a file")
class SomeModel(admin.ModelAdmin):
change_list_template = 'admin/my_app/somemodel/change_list.html'
def get_urls(self):
urls = super().get_urls()
my_urls = patterns("",
url(r"^upload_csv/$", self.upload_csv, name='upload_csv')
)
return my_urls + urls
urls = property(get_urls)
def changelist_view(self, *args, **kwargs):
view = super().changelist_view(*args, **kwargs)
view.context_data['submit_csv_form'] = SomeModelForm
return view
def upload_csv(self, request):
if request.method == 'POST':
form = MineDifficultyResourceForm(request.POST, request.FILES)
if form.is_valid():
# process form
with the template overridden as so:
{% extends "admin/change_list.html" %}
{% load i18n admin_urls admin_static admin_list %}
{% block object-tools %}
{% if has_add_permission %}
<div>
<ul class="object-tools">
{% block object-tools-items %}
<form id="upload-csv-form" action="{% url 'admin:upload_csv' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.non_field_errors }}</p>
<p>{{ submit_csv_form.as_p }}</p>
<p>{{ submit_csv_form.csv_file.errors }}</p>
<p><input type="submit" value="Upload" />
<input type="reset" value="Reset"></p>
</form>
{% endblock %}
</ul>
</div>
{% endif %}
{% endblock %}
The form needs some custom validation but otherwise this solves the difficult part of customizing the admin page.
To elaborate what is going on here:
get_urls is overridden so that an additional endpoint can be added to the admin page, this can point to any view, in this case it points upload_csv
changelist_view is overridden to append the form info to the view
the change_list.html template block "object-tools" is overridden with the form fields
Hopefully someone else finds this helpful as well.
to your class SomeModelForm add something like this:
class Meta:
model = YourModel
fields = '__all__'
and change from forms.Form to forms.ModelForm

How to delete object in django view?

I have a model Product:
class Product(models.Model):
name = models.CharField(verbose_name="name", max_length=40)
cost = models.FloatField(verbose_name="price")
def __unicode__(self):
return self.name
I created a view where i can add new products but how can i delete these products?
my idea:
def delete_product(request, pk):
if request.method == "POST":
if form.is_valid():
product = form.delete(commit=False)
product.delete()
return redirect('homeshop.views.product_list', pk=product.pk)
But what next? I added to template (where i can edit product and save it) but it does not work:
{{ delete_product }}
Now in my template:
{% block content %}
<h1>Nowy wydatek</h1>
<form method="POST" class="product-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
You would need to do something like this:
template.html
{% for product in products %}
{% csrf_token %}
...
<form action="{% url 'delete_product' product.id %}" method="POST">
<button type="submit">Delete</button>
</form>
...
{% endfor %}
then you would need to update your urls.py to have a URL defined that calls your delete function when the proper URL is visited.
url(
r'^delete/<product_id>$',
'delete_product',
name='delete_product'
)
I don't know exactly how your urls.py is laid out so your URL may have to look a little different.

django custom comment's image field not working

I'm doing django-contrib-comment's custom comment app to my django app. At long last i achieved my comment app that has image field, and it shows in template of course. But here is the thing, When i'm trying to post comment with image, it does not saving image file, and says empty(This field is required). Can anyone help me to figure it out. Here is some code snippets.
models.py
class CommentWithPic(Comment):
image = models.ImageField(upload_to="comments/%Y/%m/%d/", null=True, blank=True)
forms.py
class CommentFormWithPic(CommentForm):
image = forms.ImageField()
def get_comment_model(self):
return CommentWithPic
def get_comment_create_data(self):
data = super(CommentFormWithPic, self).get_comment_create_data()
data['image'] = self.cleaned_data['image']
return data
post_with_comment.html
{% render_comment_list for adi %}
{% get_comment_form for adi as form %}
<form action="{% comment_form_target %}" method="post">
{% csrf_token %}
{% for field in form %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% if field.errors %}{{ field.errors }}{% endif %}
{{ field.label }}
{{ field }}
{% endif %}
{% endfor %}
<input type="hidden" value="{% url 'ad' adi.id %}"/>
<input type="submit" value="comment"/>
</form>
You need to declare your form like the following (notice the enctype attribute) when dealing with `ImageField
<form enctype="multipart/form-data" action="{% comment_form_target %}" method="post">\
to bind uploads to a form:
Dealing with forms that have FileField and ImageField fields is a little more complicated than a normal form.
Firstly, in order to upload files, you’ll need to make sure that your element correctly defines the enctype as "multipart/form-data"
You also need to make the image optional in the form:
class CommentFormWithPic(CommentForm):
image = forms.ImageField(required=False)
...

Categories