how to work with Multiple forms in django Detailview - python

I have a comment section in django blog and there are two forms one is for comment another is for reply to the comment but the comment form is working fine and reply form doesn't work! i was trying to do but getting error... IntegrityError at /page/9/
FOREIGN KEY constraint failed...
appreciate to your help :)
Thank you.
views.py
class PostDetailView(DetailView):
model = Post
template_name = "post_detail.html"
context_object_name = 'post'
form = CommentForm()
def get_object(self):
obj = super().get_object()
if self.request.user.is_authenticated:
PostView.objects.get_or_create(
user=self.request.user,
post=obj
)
return obj
def get_context_data(self, **kwargs):
category_count = get_category_count()
most_recent = Post.objects.order_by('-timestamp')[:3]
context = super().get_context_data(**kwargs)
context['most_recent'] = most_recent
context['page_request_var'] = "page"
context['category_count'] = category_count
context['form'] = self.form
return context
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
form = ReplyForm(request.POST)# how to work with this form like above from
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", kwargs={
'pk': post.pk
}))
models.py
class Reply(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
content = models.TextField()
comment = models.ForeignKey('Comment', related_name='replies',default=False, null=True,
on_delete=models.CASCADE)
def __str__(self):
return self.content
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
content = models.TextField()
post = models.ForeignKey('Post', related_name='comments', default=False,
on_delete=models.CASCADE)
def __str__(self):
return self.content

You might find it easier if you did not struggle with trying to persuade a Class-based view to do what it was not intended to do, and instead used a plain old Function-based view.
Here is a two-form view. The code has been refactored into what I regard as a better pattern, to validate both forms and redisplay if anything is wrong at the top, and then you just do the actual work to create and save the objects at the bottom.
def receive_uncoated( request): #Function based view
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args )
po_form = CreateUncWaferPOForm( *args, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#POST, everything is valid, do the work
# create and save some objects based on the validated forms ...
return redirect( 'wafers:ok' )

Related

Django using a modelform to update an instance of model

I have the following model in Django which I use to store data about medicines.
class Medicine(models.Model):
Medicine_Name = models.CharField(max_length=100)
User_Associated = models.ForeignKey(User, on_delete=models.CASCADE)
Tablets_In_Box = models.IntegerField()
Dose_in_mg = models.IntegerField()
Dose_Tablets = models.IntegerField()
Number_Of_Boxes = models.IntegerField()
Last_Collected = models.DateField()
def __str__(self):
return self.Medicine_Name
def get_absolute_url(self):
return reverse('tracker-home')
I am trying to create a model form where a user can update the last collection of one of their medicines. Here is what I began with.
class CollectionForm(forms.ModelForm):
class Meta:
model = Medicine
fields = ['Medicine_Name', 'Number_Of_Boxes', 'Last_Collected']
I do not understand how I can call an instance of my model based on the 'Medicine_Name' from the field. In other words, I need the user to be able to select the correct medicine from a dropdown menu, and then the form must update the 'Last_Collected', and 'Numer_Of_Boxes' fields on my Medicine model.
https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/#the-save-method
It seems this contains relevant information, but I struggle to see how to use it in this instance. How can I correctly get the instance of the medicine form I need, based on the user input in the form? Furthermore how can I use the save method in my views to make sure the database gets updated correctly?
EDIT Added view for the form:
def update(request, pk):
instance = Medicine.objects.get(id=pk)
if request.method == 'POST':
form = CollectionForm(user=request.user, instance=instance, data=request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.User_Associated = request.user
instance.save()
else:
form = CollectionForm()
context = {'form': form}
return render(request, 'tracker/medicine_collection.html', context )
**EDIT
views:
def update(request, pk):
instance = Medicine.objects.get(id=pk)
if request.method == 'POST':
form = CollectionForm(instance=instance, data=request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.User_Associated = request.user
instance.save()
return redirect ('/')
....
This is based on updating the instance of the specific user. This tutorial helpt me achieve the same thing.
https://youtu.be/EX6Tt-ZW0so
Tried a different approach (class based views - UpdateView) I just learned here on SO. Did not test it but I think its a step in the right direction.
class UpdateMedicine(LoginRequiredMixin, UpdateView):
model = Medicine #call the model you need to update
fields = ['Medicine_Name', 'Number_Of_Boxes', 'Last_Collected'] #specify the fields you need to update
template_name_suffix = 'medicine_update_form' #specify the template where the update form is living
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(
user=self.request.user, #get the current logged in user
instance=get_object_or_404(Medicine, pk=self.kwargs['pk']) #get the pk of the instance
)
return context
def form_valid(self, form):
form.instance.medicine = get_object_or_404(Medicine, slug=self.kwargs['pk'])
return super().form_valid(form) #saves the updates to the instance
def get_success_url(self):
return reverse('medicine-collection') #name of the url where your 'tracker/medicine_collection.html is living
Link the appropriate templates and urls to the above example and try some things yourself.
Link to the django docs:
https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-editing/
Good luck!

How to implement PUT method in Django

How can I enable the user to generate only one instance of an object “bet” with a POST method and modify it through a PUT method (for example)
forms.py
class BetForm(forms.ModelForm):
team1_score = forms.IntegerField(min_value=0, max_value=15)
team2_score = forms.IntegerField(min_value=0, max_value=15)
match = forms.ModelChoiceField(queryset=Match.objects.only('id'))
class Meta:
model = Bet
fields = ('team1_score', 'team2_score', 'match')
models.py
class Bet(models.Model):
match = models.ForeignKey(Match, on_delete=models.CASCADE, related_name='+')
team1_score = models.PositiveIntegerField(default=0)
team2_score = models.PositiveIntegerField(default=0)
def __str__(self):
return (str(self.match))
views.py
def post(self, request):
form = BetForm(request.POST)
if form.is_valid():
form.save()
team1_score = form.cleaned_data.get('team1_score')
team2_score = form.cleaned_data.get('team2_score')
match = form.cleaned_data.get('match')
form = BetForm()
return redirect ('home')
args = {'form': form, 'team1_score': team1_score, 'team2_score': team2_score, 'match': match}
return render(request, self.template_name, args)
Enable the user to generate only one instance of an object “bet”...
For that, you want to add a user field to your Bet model. Here you will save a reference to the user making the request.
class Bet(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, related_name='bets', blank=True)
match = models.ForeignKey(Match, related_name='bets')
team1_score = models.PositiveIntegerField(default=0)
team2_score = models.PositiveIntegerField(default=0)
class Meta:
unique_together = ('user', 'match')
def __str__(self):
return (str(self.match))
Notice the unique_together option which makes sure a user can only create a single Bet instance for a given match.
modify it through a PUT method (for example)
Django does not automatically parse the body for PUT requests like it does for POST. Browsers normally issue POST request on forms submission. If you still want to solve it using PUT, check this post (pun intended).
Parsing Unsupported Requests (PUT, DELETE, etc.) in Django
My suggestion is to modify your post view so it accepts an optional parameter bet_id. This you can define in urlpatterns. The view would then look like this one. You retrieve the bet if bet_id is provided and pass it to the form. This way it understands the user is modifying it.
def post(self, request, bet_id=None):
if bet_id:
bet = Bet.objects.get(pk=bet_id)
form = BetForm(request.POST, instance=bet)
else:
form = BetForm(request.POST)
if form.is_valid():
bet = form.save(commit=False)
bet.user = request.user
bet.save()
# Do what you want here
Notice that we are not saving the form immediately (commit=False), so we could assign it to a user later on. This user is the logged in user from the request object.

getting the id from a foreignkey relationship django

I want to get the id or pk of a ForeignKey relationship post_comment but I've tried many different ways to catch it and i do not have any good result, please guys give me a hand in this situation
In views.py
class createComment(View):
form_class = CommentForm
template_name = "createComment.html"
def get(self, request):
form = self.form_class(None)
return render(request, self.template_name, {'form':form})
def post(self, request):
obj = self.form_class(None)
obj.title_comment = self.request.POST['title_comment']
obj.body_comment = self.request.POST['body_comment']
obj.post_comment = self.pk
obj.save()
In models.py
class Comment(models.Model):
user_comment = models.ForeignKey("auth.User")
title_comment = models.CharField(max_length=50)
body_comment = models.TextField()
timestamp_comment = models.DateTimeField(auto_now=True)
post_comment = models.ForeignKey("Post", null=True)
status_comment = models.BooleanField(default=True)
def __unicode__(self):
return unicode(self.title_comment)
def __str__(self):
return self.title_comment
You can pass a primary key in the url, and then use it in your class as one way.
kwargs.get(pk name)
You could change post to:
def post(self, request, *args, **kwargs)
You then can't just assign obj.post_comment = kwargs.get(pk) you have to actually get the object.
Post.objects.get(pk = pk)
You might want to also consider renaming fieldname_comment to just fieldname for your models fields. Seems a bit redundant to have _comment on every single field in the Comment model.
I don't know how works class based views but I can tell you that self.pk does not exist in class based view, you would try get form instance and get the I'd field from this instance...

KeyError at / object_post//occuring because of python?or JSON?

Hello I'm getting KeyError at object_post for some reason.
I believe keyError means it's not importing data correctly, but I have no idea what's wrong with the code. If anyone has an idea I would appreciated it. My guess is my python code is wrong, but I googled around and some one says it's JSON problem. I'm not an expert in JSON(so it will be bigger prob if it's the case)...I'll post my code assuming there's something wrong with my code.
Error MEssage: KeyError at /'object_list'
Code:
views.py
for front page
class IndexView(TemplateView):
template_name = 'main/index.html'
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
voted = Vote.objects.filter(voter=self.request.user)
posts_in_page = [post.id for post in context["object_list"]]
voted = voted.filter(post_id__in=posts_in_page)
voted = voted.values_list('post_id', flat=True)
context.update({
'voted' : voted,
'latest_posts': Post.objects.all().order_by('-created_at'),
'popular_posts': Post.objects.all().order_by('-views'),
'hot_posts': Post.objects.all().order_by('-score')[:25],
'categories': Category.objects.all(),
})
return context
Models.py
class Post(models.Model):
category = models.ForeignKey(Category)
created_at = models.DateTimeField(auto_now_add = True)
title = models.CharField(max_length = 100)
content = FroalaField()
url = models.URLField(max_length=250, blank=True)
moderator = models.ForeignKey(User, default="")
rank_score = models.FloatField(default=0.0)
with_votes = PostVoteCountManager()
views = models.IntegerField(default=0)
image = models.ImageField(upload_to="images",blank=True, null=True)
slug = models.CharField(max_length=100, unique=True)
objects = models.Manager() # default manager
def save(self, *args, **kwargs):
self.slug = uuslug(self.title, instance=self, max_length=100)
super(Post, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
# for redirecting URL so slug is always shown
def get_absolute_url(self):
return '/%s/%s' % (self.id, self.slug)
def set_rank(self):
# Based on HN ranking algo at http://amix.dk/blog/post/19574
SECS_IN_HOUR = float(60*60)
GRAVITY = 1.2
delta = now() - self.submitted_on
item_hour_age = delta.total_seconds() // SECS_IN_HOUR
votes = self.votes - 1
self.rank_score = votes / pow((item_hour_age+2), GRAVITY)
self.save()
class Vote(models.Model):
voter = models.ForeignKey(User)
post = models.ForeignKey(Post)
def __unicode__(self):
return "%s voted %s" % (self.voter.username, self.post.title)
Complete Error:
KeyError at /
'object_list'
Request Method: GET
Request URL: http://127.0.0.1:8000/
Django Version: 1.8.4
Exception Type: KeyError
Exception Value:
'object_list'
Exception Location: /home/younggue/Desktop/ebagu0.2/rclone/main/views.py in get_context_data, line 25
Python Executable: /home/younggue/Desktop/ebagu0.2/env/bin/python
Edit3:
my modified view
class IndexListView(ListView):
model = Post
queryset = Post.with_votes.all()
template_name = 'main/index.html'
def get_context_data(self, **kwargs):
context = super(IndexListView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
voted = Vote.objects.filter(voter=self.request.user)
posts_in_page = [post.id for post in context["object_list"]]
voted = voted.filter(post_id__in=posts_in_page)
voted = voted.values_list('post_id', flat=True)
context.update({
'voted' : voted,
'latest_posts': Post.objects.all().order_by('-created_at'),
'popular_posts': Post.objects.all().order_by('-views'),
'hot_posts': Post.objects.all().order_by('-score')[:25],
'categories': Category.objects.all(),
})
errors occur from here
#login_required
def add_category(request):
if request.method == 'POST':
form = CategoryForm(request.POST)
if form.is_valid():
form.save(commit=True)
return IndexListView(request)
else:
print form.errors
else:
form = CategoryForm()
return render(request, 'main/add_category.html', {'form':form})
I provided one argument request and the error says I provided two: TypeError at /add_category/
""__init__() takes exactly 1 argument (2 given).
So I googled it and people say it's from urls.py
urls.py """
urlpatterns = [
url(r'^$', IndexListView.as_view(), name='index'),
#url(r'^add_post/', views.add_post, name='add_post'),
url(r'^add_post/$', PostCreateView.as_view(), name='post-add'),
url(r'^(?P<slug>[\w|\-]+)/edit/$', PostUpdateView.as_view(), name='post-edit'),
url(r'^(?P<slug>[\w|\-]+)/delete/$', PostDeleteView.as_view(), name='post-delete'),
url(r'^add_category/', views.add_category, name='add_category'),
url(r'^(?P<slug>[\w|\-]+)/$', views.post, name='post'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/$', CategoryDetailView.as_view(), name='category'),
]
I have IndexListView.as_view().
why is this error happening?
Your view inherits from TemplateView so nobody fills up the object_list entry in the context.
Instead, you might want to inherit from ListView or other generic CBVs which populate such context key
As described there:
This template will be rendered against a context containing a variable
called object_list that contains all the publisher objects.
you can see it in the code for ListView
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
...
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
As you can see, the get method sets self.object_list to the queryset retrieved and then the get_context_data method uses it to update the context for the template.
On the contrary, TemplateView does not perform such steps:
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
if 'view' not in kwargs:
kwargs['view'] = self
return kwargs
so the object_list key does not exist in the context dicitonary.
Of course, in case for some reason you prefer not to use Listview you can still perform such steps in your code, overriding the get and get_context_data methods or inheriting from the proper mixins, e.g.
class IndexView(TemplateResponseMixin, MultipleObjectMixin , View):
Given your latest edit:
in add_category you do
return IndexListView(request)
which makes little sense (i.e. returning an instance of a view).
I presume you are trying to redirect to the index page, so that can be achieved with
return redirect(reverse_lazy('index'))

Django User foreign key in View vs in model.save() method

I have the following model (simplified):
class Candidate(models.Model):
""" Model for candidate clients """
# fields
general_category = models.ForeignKey('GeneralCategory',
related_name='candidate',
null=True,
blank=True,
# default=1,
verbose_name='Γενική Κατηγορία',)
brand_name = models.CharField(max_length=160,
blank=True,
verbose_name='Επωνυμία')
creation_date = models.DateTimeField(null=True, blank=True, verbose_name='Πρώτη καταχώρηση')
last_edited = models.DateTimeField(null=True, blank=True, verbose_name='Τελευταία επεξεργασία')
first_edited_by = models.ForeignKey(User,
related_name='first_edited_candidates',
blank=True,
null=True,
verbose_name='Πρώτη επεξεργασία από',)
last_edited_by = models.ForeignKey(User,
related_name='last_edited_candidates',
blank=True,
null=True,
verbose_name='Τελευταία επεξεργασία από',)
def save(self, *args, **kwargs):
""" On save, update timestamps and user fields """
if 'request' in kwargs:
request = kwargs.pop('request')
else:
request = None
if not self.id:
self.creation_date = timezone.now()
self.last_edited = timezone.now()
if request is not None:
if not self.first_edited_by:
self.first_edited_by = request.user
self.last_edited_by = request.user
log.info(self)
return super(Candidate, self).save(*args, **kwargs)
def __str__(self):
return self.brand_name + '[' + str(self.__dict__) + ']'
If I fire up the debugger in PyCharm I can see that the two User foreign keys are populated as expected in my detail view, but inside the model.save() method they are None. The other foreign key (general_category) is populated as expected.
Why is that? Does it have something to do with the self keyword?
My view (again, simplified) is this:
#login_required
#require_http_methods(["GET", "POST"])
def candidate_detail(request, candidate_id):
candidate = get_object_or_404(Candidate, pk=candidate_id)
original_http_referrer = request.GET.get('next')
if request.method == 'GET':
form = CandidateForm(instance=candidate)
elif request.method == 'POST':
form = CandidateForm(request.POST, instance=candidate)
if form.is_valid():
candidate.save(request=request)
return HttpResponseRedirect(original_http_referrer)
# else:
# TODO: show some error message ?
context = {'candidate': candidate,
'form': form,
'original_http_referrer': original_http_referrer}
return render(request, 'candidates/candidate_detail.html', context)
I'm using Django 1.8 with Python 3.4.
UPDATE: It seems that the value of the foreign keys is lost in the line
form = CandidateForm(request.POST, instance=candidate)
The weird thing is that, if I step-in and go line-by-line with the debugger, my program ends up working as expected! (I have also tried this using manage.py runserver to make sure it is not a bug in the PyCharm's server implementation and it's not)
I'll try logging my model at each step tomorrow to narrow down the offending code. Just to make sure, here is my form's code (not simplified):
from django.forms import ModelForm
from candidates.models import Candidate
class CandidateForm(ModelForm):
class Meta:
model = Candidate
fields = '__all__'
You didn't save the form.
if form.is_valid():
candidate = form.save(commit=False)
candidate.save(request=request)
Note that the first four lines of the save method can be simplified to one:
request = kwargs.pop('request', None)

Categories