Django not saving form data - python

I've made a simple test django project that's a simple list of posts. And I'm trying to add functionality to update a post. Everything seems to be working, except the edits aren't saved to the database.
I've checked the cleaned data to see if the updated data is coming through, and it is, but the save() function doesn't seem to actually do anything.
models.py
class Block(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=140, null=True, blank=True)
content = models.TextField()
def save(self, *args, **kwargs):
if self.slug is None:
self.slug = get_unique_slug(self, 'title', 'slug')
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("bulkapp:one_view", kwargs={"slug_id": self.slug})
def __str__(self):
return self.title
views.py
def edit_block_view(request, slug_id):
single_block_query = get_object_or_404(Block, slug=slug_id)
update_form_query = BlockForm(request.POST or None, instance=single_block_query)
if update_form_query.is_valid():
update_form_query.save()
return redirect('bulkapp:one_view', slug_id=slug_id)
return render(request, 'bulkapp/update.html', {'update_form': update_form_query})
<form class="form-container" method="POST">
{% csrf_token %}
{{update_form.as_p}}
<input type="submit" value="Edit">
</form>
Edit:
forms.py
class BlockForm(forms.ModelForm):
class Meta:
model = Block
fields = [
"title",
"slug",
"content",
]
The redirect fires as expected, but no changes are saved, and no error messages are written to the console. Any help would be much appreciated.

The problem was the indentation of the super().save(*args, **kwargs) in my save() function which didn't execute if it the slug was not null.

Related

Struggling to get 'get absolute url' to work (Python - Django)

Once a user had logged into my site he could write a post and update it.
Then I was making progress in adding functionality which allowed people to make comments. I was at the stage where I could add comments from the back end and they would be accurately displayed on the front end.
Now when I try and update posts I get an error message.
I assume it is because there is a foreign key linking the comments class to the post class. I tried Googling the problem and looking on StackOverflow but I wasn't entirely convinced the material I was reading was remotely related to my problem. I am struggling to fix the issue because I barely even understand / know what the issue is.
models.py
def save(self, *args, **kwargs):
self.url= slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', kwargs={'slug': self.slug})
class Comment(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE,related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=False)
class Meta:
ordering = ['created_on']
def __str__(self):
return 'Comment {} by {}'.format(self.body, self.name)
def get_absolute_url(self):
return reverse('article_detail', kwargs={'slug': self.slug})
views.py
def post_detail(request, pk):
template_name = 'post_detail.html'
comments = Comment.objects.filter(post=pk ,active=True)
post = Post.objects.get(pk=pk)
new_comment = None
# Comment posted
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# Create Comment object but don't save to database yet
new_comment = comment_form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
# Save the comment to the database
new_comment.save()
else:
comment_form = CommentForm()
return render(request, template_name, {'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
You have no field in your model Comment called slug (aka SlugField) so it's not going to work.
The save method should be defined in the class also - see here. You also don't need the get_absolute_url under save() either.

Django: access generic UpdateView with slug instead of pk

The code for using the pk is the following:
views.py
class CreatorUpdate(LoginRequiredMixin, UpdateView):
model = Creator
fields = ['first_name', 'last_name', 'location', 'country']
success_url = reverse_lazy('index')
# these two methods only give access to the users own profile but not the others (prevents url hacking)
def user_passes_test(self, request):
if request.user.is_authenticated:
self.object = self.get_object()
return self.object.user == request.user
return False
def dispatch(self, request, *args, **kwargs):
if not self.user_passes_test(request):
return redirect_to_login(request.get_full_path())
return super(CreatorUpdate, self).dispatch(request, *args, **kwargs)
urls.py
path('creator/<int:pk>/update/', views.CreatorUpdate.as_view(), name='creator-update')
HTML snippet to call the URL:
{{ user.get_username }}
Now I would like to use a slug (username) instead of the pk to access the UpdateView. I can successfully pass the username to the URL:
{{ user.get_username }}
but seem not to be able to match it in urls.py
path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')
with the same logic. I also tried to set a SlugField in my Creator model like this:
from django.template.defaultfilters import slugify
class Creator(models.Model):
...
username = models.CharField(max_length=100)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.username)
super(Creator, self).save(*args, **kwargs)
...
And setting slug_field = 'slug' in the UpdateView also does not get it.
These modification result in a 404 error: raised by catalog.views.CreatorUpdate: No creator found matching the query
When adjusting the HTML part to {{ user.get_username }} I get a NoReverseMatcherror Reverse for 'creator-update' with arguments '('',)' not found.
What is the logic to use a slug to access a view?
The error is that the user object has no attribute slug. The Creator model has the slug field. Create a ListView of Creator Model then check if each creator object has a slug.
Correct models.py:
class Creator(models.Model):
...
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.user)
super(Creator, self).save(*args, **kwargs)

Django auto-generated unique field failing validation on form edit

I am attempting to reuse my create form (EntryForm) for editing a model in Django. My Entry model has a unique slug that is generated on save. This works fine when creating an Entry, but shows the following error when I attempt to edit it:
Entry with this Slug already exists.
I saw several similar questions, but most were failing to set instance= when instantiating the form. I'm pretty sure I'm doing that part correctly.
I've removed other model fields from the code below for clarity.
Here is my model:
class Entry(models.Model):
title = models.CharField(max_length=128, blank=True)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.title:
self.title = self.date.strftime('%B %-d, %Y')
self.slug = slugify(self.title)
super(Entry, self).save(*args, **kwargs)
My view:
def edit_entry(request, entry_slug):
entry = get_object_or_404(Entry, slug=entry_slug)
form = EntryForm(instance=entry, label_suffix='')
if request.method == 'POST':
form = EntryForm(request.POST, instance=entry, label_suffix='')
if form.is_valid():
form.save(commit=True)
return index(request)
else:
print(form.errors)
return render(request, 'journal/entry/form.html', {'form': form})
My form:
class EntryForm(forms.ModelForm):
title = forms.CharField(required=False, max_length=128, label="Title (defaults to date)")
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta:
model = Entry
exclude = ()
Any ideas?
I did finally figure this out.
The issue was stemming from the fact that I was reusing my create template for the edit form, but forgot to set the form action dynamically depending on which action I desired. So, my 'edit' form was rendering correctly, but actually submitting via the 'create' action.
Thanks to those who commented and to what ultimately led me to debugging the problem, this handy code snippet equivalent to Ruby's binding.pry:
import code; code.interact(local=dict(globals(), **locals()))
I did also take #xyres's advice and remove the slug from my form, as it was unnecessary.
New form:
class EntryForm(forms.ModelForm):
title = forms.CharField(required=False, max_length=128, label="Title (defaults to date)")
class Meta:
model = Entry
exclude = ['slug']
New final line of edit_entry():
return render(request, 'journal/entry/form.html', {'form': form, 'entry': entry})
Form action:
{% if entry %}
<form id="entry_form" method="post" action="/journal/entry/{{ entry.slug }}/edit">
{% else %}
<form id="entry_form" method="post" action="/journal/new_entry/">
{% endif %}

Django queries return empty strings

I have a problem with my django app, i'm created two Blog objects, one in mysql and another one with my view. I see them both in my database but i can't see them when i get them with a query.
I'm using django 1.9, python 2.7, apache 2.2 with mod_wsgi
Here is my view and my template.
def list_view(request):
blogs = Blog.objects.filter(published=True)
return render_to_response('blog/list.html',
{
"blogs": blogs,
},
context_instance=RequestContext(request))
{% for blog in blogs %}
<h2>{{blog.title}}</h2>
<p>{{blog.content}}</p>
<span>{% trans 'Writen by' %} : {{blog.writer.last_name}} {{blog.writer.first_name}}</span> - <span>{% trans 'Published on' %} {{blog.date_published}}</span>
{% endfor %}
The query gets me a list with 2 Blog objects inside, but they are empty. My template just shows Writen By - Published on twice.
But i can log in and print all my user informations.
Any idea what could be the problem or how i could solve it ? Thanks a lot !
EDIT : add models.py
class Blog(models.Model):
title = models.CharField(_("Title"), max_length=255, null=False, blank=False)
content = models.TextField(_("Content"), null=True, blank=True)
writer = models.ForeignKey(User, verbose_name=_('Writer'), blank=False, null=False)
published = models.BooleanField(_("Pusblished"), default=False)
date_published = models.DateTimeField(_("Date published"))
def __str__(self):
return '%s' % (self.title)
def __init__(self, *args, **kwargs):
super(Blog, self).__init__()
When you overrode Blog.__init__(), you did not send *args and **kwargs to the parent. Django Model magic needs those parameters. If you're not doing anything in __init__() you could just remove that all together.
If you want to keep it, do something like:
def __init__(self, *args, **kwargs):
super(Blog, self).__init__(*args, **kwargs)

Django - Commands and context dictionaries

I´m trying to get a context_dictionary from specific urls to use in a django command. Code without the command returns the model with a slug and details to a site, let´s suppose it is localhost/event/mainevent/.
Once I want to get the context dictionary used for that site,how do I get it in my command?
My code so far is the following:
Commands/MyCommand.py
class Command(BaseCommand):
help = 'Closes the specified poll for voting'
def handle(self, *args, **options):
#Get context processor here
self.stdout.write(event.title)
Models.py
class Event(models.Model):
title = models.CharField(max_length=255, unique=True)
location = models.CharField(max_length=300)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Event, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
Views.py
def details(request, event_title_slug):
context_dict = {}
try:
event = Event.objects.get(slug=event_title_slug)
context_dict['event_title'] = event.title
context_dict['event'] = event
except Event.DoesNotExist:
pass
return render(request, 'event_details.html', context_dict)
Urls.py
url(r'^event/(?P<event_title_slug>[\w\-]+)/$', views.details, name='details')
I don't understand what you mean by context dictionary here. The context in a view is for rendering a template; you don't want to do that in your command. And in any case you don't have a URL in the command, so there is no relationship to the view.
Instead you need to pass an argument, say the slug, to your command, and use that to get the event to delete. The documentation has an example that does almost exactly what you want.

Categories