Add ManyToManyField relationship on save, and create objects if they not exist - python

I want to 'attach' a manytomany relationship on form submit.
The example is the classic blog tags-post relation: a post can have multiple tags related
In django-admin it works, but i can't figure how to do in views...
my code:
def add_post(request):
if request.method == 'POST':
form = PostForm(data=request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.author = request.user
newentry = model_instance.save()
#some magic needed here, this will never works
for tag in model_instance.tags.all():
t = Tag.objects.get_or_create(author=request.user, title=tag.title, slug=slugify(tag.title))
model_instance.tags.add(t)
#end of magic
return HttpResponseRedirect("/blog/")
else:
form = PostForm()
return render_to_response(
'blog/add_post.html',
{'form' : form },
context_instance=RequestContext(request))
I'm using the automatic tokenization from select2.js in the template, so i can to add tags "on the fly", but i'm not sure how to handle it with django...

newentry = model_instance.save()
The save() method on a model instance will not return anything, unlike the same method on a form. You should reuse model_instance instead of using newentry.
I'd also take a look at get_or_create() to create the tag if it doesn't exist already.

I have a blog where I have a ManyToManyField between posts and projects, and I select the projects to create a relationship to in the post form.
When you use commit=False on form.save(), you need to use form.save_m2m() after the form is saved using post.save().
This is what part of my view function looks like:
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
# This saves the project relation
form.save_m2m()
For your usage, I would try moving the model_instance.save() and form.save_m2m() after your magic.
Another thing about get_or_create() is that it returns a tuple with the object that was gotten or created, and a boolean in regards to whether it created the object. So you need to only pass model_instance.tags.add() the created object.
form = PostForm(data=request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.author = request.user
#some magic needed here, this will never works
for tag in model_instance.tags.all():
t, created = Tag.objects.get_or_create(author=request.user, title=tag.title, slug=slugify(tag.title))
model_instance.tags.add(t)
#end of magic
model_instance.save()
form.save_m2m()

As a database programmer (with a little django experience) I would argue that you should normalize your models. IE, add a third table (call it something like Post Tags) and set it up as an intermediary between posts and tags. So the relationship from Post to Post Tags would be 1:M and the relationship from Tags to Post Tags would be 1:M

Related

Model field not updating in admin page even if value changes

I apologize for my confusing title but I hope the code explains it better.
In my views.py file I have the follow view
def create_view(request):
context = {}
form = CreateForm(request.POST)
if request.method == "POST":
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
instance.author.profile.participating_in = Post.objects.get(
title=instance.title
)
instance.save()
print(instance.author.profile.participating_in)
context["form"] = form
return render(request, "post/post_form.html", context)
when I print out the value of instance.author.profile.participating_in it shows up in my terminal however when I check the admin page it doesnt update at all. I'm sure I messed up somewhere silly but I cant seem to find it. Thanks!
participating_in is the profile model field, but you are not calling the save() method for profile anywhere.
You have to do it like the following:
profile = instance.author.profile
profile.participating_in = Post.objects.get(title=instance.title)
profile.save()
If participating_in is ManyToManyField then we can do it like this:
post = Post.objects.get(title=instance.title)
instance.author.profile.participating_in.add(post)
Note that add(), create(), remove(), clear(), and set() all
apply database changes immediately for all types of related fields. In
other words, there is no need to call save() on either end of the
relationship.
Look at Related objects reference

How Do I Have Multiple ModelForms In Django in A SingleView?

I am trying to create A Project where I Have A model Form Which Takes A set of data as Input.
However that is not an issue, How Do I Have Multiple Forms of that One single Model Form .
I tried Using something like this
from .forms import BookForm
# Create your views here.
def home_page(request):
context = {}
form = BookForm(request.POST or None)
form1 = BookForm(request.POST or None)
if form.is_valid():
form.save()
if form1.is_valid():
form1.save()
context['form']= form
context['form1']= form1
return render(request, "home.html", context)
But What Happens is the Data passed in the last form is passed in all of the forms.
How Do I Implement this in Django Handling multiple forms in single view, which gets submitted on click of a button
You can use formsets in django.
Since you are using a model form, you can use something known as model formsets using something known as modelformset_factory.
Basically formsets help you in having a list of forms. Imagine your single form now turned into a list of forms.
Checkout this link for a tutorial
Let me know if you want more explanation

in Django, how to implement CreateView's function with based-function views?

here is based-class views code:
# views.py
class ObjectCreate(CreateView):
model = ObjectModel
fields = "__all__"
its simple to create an object and save it use this class.
I wonder how?
what if I want to use based-function views to achieve it?
Using a function view you would need to implement everything, including creating a form for your model:
def create_object(request):
if request.method == 'GET':
form = ObjectForm()
if request.method == 'POST':
form = ObjectForm(request.POST)
if form.is_valid():
instance = form.save() # instance created
# now redirect user or render a success template
return redirect(...)
# if request method is GET or form is invalid return the form
return render(request, 'path/template_name.html', {'form': form})
If you want to learn how the CreateView works, look at its source code. Or for easier overview of the structure, look at this site which lists all the Django CBVs.
You'll find that CreateView inherits from 9 other classes, has about 20 attributes (of which model and fields) and 24 methods that you can override to customise its behaviour.

set the value of a django HiddenInput widget to another object's id rendered in the template

Imagine we have a detail page for the blog posts of a blog and we accept comments on this page, now we need to know which post we are commenting on, in our views so we can make a comment object for that post.
How is possible to set the {{ post.id }} in a HiddenInput widget value so we can then use it in our comment views
I tried to manually add this to my html form but I want to use form template tags so I can validate the form later:
<input type="hidden" name="post_comment" value="{{post.id}}>
forms.py:
comment_post = forms.Field(widget=forms.HiddenInput())
views.py:
def comment(request):
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
Comment.objects.create(text=form.cleaned_data['comment_text'],post=form.cleaned_data['comment_post'] ,cm_author=request.user)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
In general I'd do this by setting the post ID based on something other than a form value. In order to set the post to comment relationship your view has to know which post is being commented on - probably as a URL element - so I'd just use that directly rather than passing it around as form data. Something like:
from django.shortcuts import get_object_or_404
def comment(request, post_id):
post = get_object_or_404(Post, id=post_id)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.post = post
new_comment.save()
# usual form boilerplate below here
One way you could implement this in your uris.py is:
uris:
url(r'(?P<post_id>\d+)/$', views.comment, name="comment")
Depending on the rest of your URL structure it might be clearer to include more context:
url(r'post/(?P<post_id>\d+)/comment/$', views.comment, name="comment")
But that's basically down to personal preference unless you're aiming for a REST-style API.
There's no need to specify the form action differently - the usual action="" will submit the form to a URI that includes the ID, since it's part of the URI that displays the form.
If for some reason you want to do this with the hidden input, use an initial argument when creating the form.
if request.method == 'POST':
form = CommentForm(request.POST, initial={'comment_post': post})
# usual processing
else:
form = CommentForm(initial={'comment_post': post})
# and render
I assume your model Comment has a foreign key relationship with Post, you can just use forms.ModelChoiceField for comment_post:
comment_post = forms.ModelChoiceField(queryset=Post.objects.all(),
widget=forms.HiddenInput())
In the views, give current post object as initial data to the comment_post:
# assume you have a variable that called "current post"
comment_form = CommentForm(request.POST, initial={'comment_post': current_post})
Just in case you are not sure what's the behavior, it's going to create a hidden form field and pre-populate selected data with current_post, then when you POST the form, the form already contains the data, you call comment_form.save() and that's it.

Django edit form based on add form?

I've made a nice form, and a big complicated 'add' function for handling it. It starts like this...
def add(req):
if req.method == 'POST':
form = ArticleForm(req.POST)
if form.is_valid():
article = form.save(commit=False)
article.author = req.user
# more processing ...
Now I don't really want to duplicate all that functionality in the edit() method, so I figured edit could use the exact same template, and maybe just add an id field to the form so the add function knew what it was editing. But there's a couple problems with this
Where would I set article.id in the add func? It would have to be after form.save because that's where the article gets created, but it would never even reach that, because the form is invalid due to unique constraints (unless the user edited everything). I can just remove the is_valid check, but then form.save fails instead.
If the form actually is invalid, the field I dynamically added in the edit function isn't preserved.
So how do I deal with this?
If you are extending your form from a ModelForm, use the instance keyword argument. Here we pass either an existing instance or a new one, depending on whether we're editing or adding an existing article. In both cases the author field is set on the instance, so commit=False is not required. Note also that I'm assuming only the author may edit their own articles, hence the HttpResponseForbidden response.
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render, reverse
#login_required
def edit(request, id=None, template_name='article_edit_template.html'):
if id:
article = get_object_or_404(Article, pk=id)
if article.author != request.user:
return HttpResponseForbidden()
else:
article = Article(author=request.user)
form = ArticleForm(request.POST or None, instance=article)
if request.POST and form.is_valid():
form.save()
# Save was successful, so redirect to another page
redirect_url = reverse(article_save_success)
return redirect(redirect_url)
return render(request, template_name, {
'form': form
})
And in your urls.py:
(r'^article/new/$', views.edit, {}, 'article_new'),
(r'^article/edit/(?P<id>\d+)/$', views.edit, {}, 'article_edit'),
The same edit view is used for both adds and edits, but only the edit url pattern passes an id to the view. To make this work well with your form you'll need to omit the author field from the form:
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
exclude = ('author',)
You can have hidden ID field in form and for edit form it will be passed with the form for add form you can set it in req.POST e.g.
formData = req.POST.copy()
formData['id'] = getNewID()
and pass that formData to form

Categories

Resources