How to handle object reference in CreateView - python

I have an item. An item can have many reviews. I expect a review to be created within the context of an item. Therefore, I capture the pk from the url and add it to the context.
This is where I get stuck, I'm unsure how to access the context in form_valid and, more importantly, I'm concerned the path I'm trying to go down seems hacky.
Essentially when the user prepares to submit a review, the application will know what item it's in reference to. What's the most pythonic/django-onic way to do this?
Models
class Item(models.Model):
name = models.CharField(max_length=100)
source = models.ForeignKey('Source')
class Review(models.Model):
rating = models.CharField(max_length=30)
value = models.CharField(max_length=30)
date = models.DateField(auto_now_add=True)
comment = models.CharField(blank=True,max_length=100)
item = models.ForeignKey(Item,blank=True)
user = models.ForeignKey(User)
Urls
url(r'^review/create/item/(?P<itempk>\d+)',views.ReviewCreate.as_view(),name='review_create'),
Views
class ReviewCreate(CreateView):
model = Review
fields = ['rating', 'value', 'comment']
def get_context_data(self, **kwargs):
context = super(ReviewCreate, self).get_context_data(**kwargs)
itempk = self.kwargs['itempk']
item = get_object_or_404(Item, pk=itempk)
context['item'] = item
return context
def form_valid(self, form):
review = form.save(commit=False)
review.user = self.request.user
context = super(ReviewCreate, self).get_context_data(**kwargs) '''doesn't work'''
review.item = context['item']
return super(ReviewCreate, self).form_valid(form)
template_name = 'food/review_form.html'

The get_context_data method is meant to return the context for a template, so I agree that calling it in form_valid is a bit hacky.
You could fetch the item in the dispatch method instead and store it as self.item. Then you can retrieve the item in get_context_data and form_valid.
In form_valid you can modify form.instance - that way you don't have to save with commit=False.
class ReviewCreate(CreateView):
model = Review
fields = ['rating', 'value', 'comment']
def dispatch(self, request, *args, **kwargs):
itempk = self.kwargs['itempk']
self.item = get_object_or_404(Item, pk=itempk)
return super(ReviewCreate, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(ReviewCreate, self).get_context_data(**kwargs)
context['item'] = self.item
return context
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.item = self.item
return super(ReviewCreate, self).form_valid(form)
template_name = 'food/review_form.html'

Related

How to override the update action in django rest framework ModelViewSet?

These are the demo models
class Author(models.Model):
name = models.CharField(max_lenght=5)
class Post(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_lenght=50)
body = models.TextField()
And the respective views are
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
I am trying to perform an update/put action on PostViewSet and which is succesfull, but I am expecting different output. After successful update of Post record, I want to send its Author record as output with AuthorSerializer. How to override this and add this functionality?
You can override update method for this:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
# this will return autor's data as a response
return Response(AuthorSerializer(instance.parent).data)
I figured out some less code fix for my issue.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostStatSerializer
def update(self, request, *args, **kwargs):
super().update(request, *args, **kwargs)
instance = self.get_object()
return Response(AuthorSerializer(instance.author).data)

Post comment inside Django DetailView

I need to add comments in article page. Post request receives data but not saves it.
In models.py:
class TemporaryComment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='temporary_comment')
email = models.EmailField()
message = models.TextField(max_length=1500)
timestamp = models.DateTimeField(auto_now_add=True)
is_approved = models.BooleanField(default=True)
In forms.py:
class CommentModelForm(forms.ModelForm):
class Meta:
model = TemporaryComment
fields = [
'article',
'email',
'message',
]
in views.py:
class ArticleDetailView(FormMixin, DetailView):
model = Article
template_name = 'article.html'
form_class = CommentModelForm
def get_success_url(self):
return reverse_lazy('main:article', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['last_articles'] = Article.objects.filter(is_active=True).order_by('-timestamp')[:10]
context['comments'] = self.object.temporary_comment.filter(is_approved=True)
context['form'] = self.get_form()
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return super().form_valid(form)
If i comment out article field in forms.py, i got this error:
null value in column "article_id" violates not-null
constraint. DETAIL: Failing row
contains (18, username#email.com, check message, 2019-01-20 18:35:36.615955+00, t, null).
Help will be gladly accepted. Thanks for your time.
You should comment it out, and set it in form_valid.
def form_valid(self, form):
form.instance.article = self.object
form.save()
return super().form_valid(form)

formset validation in CBV

I have an Order form where I used formsets because I want the user to dynamically add more products and their quantity.
The user enters the name of the order, and it is validated not to be empty.
How do I validate the formset itself? to check that the product/quantity is not empty
views.py
class OrderCreateView(CreateView):
model = Order
template_name = "orderform.html"
fields = ['name', ]
def get_context_data(self, **kwargs):
data = super(OrderCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['productmetas'] = InlineOrderFormSet(self.request.POST)
return data
def form_valid(self, form):
context = self.get_context_data()
productmetas = context['productmetas']
self.object = form.save(commit=False)
self.object.save()
if productmetas.is_valid():
productmetas.instance = self.object
productmetas.save()
return super(OrderCreateView, self).form_valid(form)
models.py
class ProductMeta(models.Model):
order = models.ForeignKey(Order)
product = models.ForeignKey(Product)
quantity = models.FloatField()
forms.py
InlineOrderFormSet = inlineformset_factory(Order, ProductMeta,
form=OrderAutoCompleteForm,
extra=1)
For anyone interested in the working solution for the question above
adapted from the answer by #neverwalkaloner
def form_valid(self, form):
context = self.get_context_data()
productmetas = context['productmetas']
with transaction.atomic():
self.object = form.save()
if form.is_valid():
if productmetas.is_valid():
productmetas.instance = self.object
productmetas.save()
else:
return super(OrderCreateView, self).form_invalid(form)
return super(OrderCreateView, self).form_valid(form)

Django - Query to retrieve pk from other model

I am trying to create a query to grab the pk of the current post from the database. Then set it as the foreign key of the new post. I am using formview, and the model I am trying to retrieve the 'id' from is called Projects. Id is the primary key of the model Projects.
How would I be able to go about this?
pk=5 because I didn't know how to get the current one.
views.py
class ProjectDetailToDoForm(FormView):
model = ProjectsToDo
form_class = ProjectToDoForm
success_url = '../..'
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(ProjectDetailToDoForm, self).dispatch(request, *args, **kwargs)
def form_valid(self,form):
self.object = form.save(commit=False)
self.object.project = Projects.objects.get(pk=5)
self.object.save()
return super(ProjectDetailToDoForm, self).form_valid(form)
class ProjectDetail(generic.DetailView):
model = Projects
context_object_name = 'indprojects'
template_name = 'projectpage.html'
def get_context_data(self, *args, **kwargs):
context = super(ProjectDetail, self).get_context_data(*args, **kwargs)
context['todolist'] = ProjectsToDo.objects.order_by('project_tododate')
context['todoform'] = ProjectToDoForm()
context['form'] = ProjectForm(instance=Projects.objects.get(slug=self.kwargs['slug']))
return context
def get_queryset(self):
return Projects.objects.filter(user=self.request.user)
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(ProjectDetail, self).dispatch(request, *args, **kwargs)
models.py
class Projects(models.Model):
user = models.ForeignKey(User)
slug = models.SlugField()
project_title = models.CharField(max_length=30)
project_shortdesc = models.CharField(max_length=248)
project_desc = models.TextField()
def save(self):
super(Projects, self).save()
date = datetime.date.today()
self.slug = '%i%i%i%s' % (
date.year, date.month, date.day, slugify(self.project_title)
)
super(Projects, self).save()
class ProjectsToDo(models.Model):
project_tododate = models.DateField()
project_tododesc = models.TextField(max_length = 500)
project = models.ForeignKey(Projects)
def __unicode__(self):
return '%s %s' % (self.project_tododesc, self.project_tododate)
I am guessing that you define the active project using the url. In that case, you can do something like this:
urls.py
url(r'^(?P<project_slug>[\w-]+)/add_todo/$',
views.ProjectDetailToDoForm.as_view(),
name='add_todo',
),
...
view
def form_valid(self, form):
self.object = form.save(commit=False)
project = Project.objects.get(slug=self.kwargs["project_slug"])
self.object.project = project
self.object.save()
return super(ProjectDetailToDoForm, self).form_valid(form)

Django's CreateView is not saving an object

I'm practicing Django's generic views, particularly ModelForms
These are my views and models
models.py
class Post(models.Model):
user = models.ForeignKey(User)
post_title = models.CharField(max_length=200)
post_content = models.CharField(max_length=500)
post_date = models.DateTimeField('date posted')
def get_absolute_url(self):
return reverse('user-detail', kwargs={'pk': self.pk})
def __unicode__(self):
return self.post_title
forms.py
class PostForm(forms.ModelForm):
post_title = forms.CharField(
label=u'Title',
widget=forms.TextInput(attrs={'size':64})
)
post_content = forms.CharField(
label=u'Content',
widget=forms.TextInput(attrs={'size':128})
)
class Meta:
model = Post
views.py
class PostCreate(CreateView):
fields = ['post_title', 'post_content']
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(PostCreate, self).dispatch(*args, **kwargs)
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.post_date = datetime.now()
return super(PostCreate, self).form_valid(form)
The view seems to work fine, not displaying any errors. However, when I check the Django admin page after submitting a form and saving a post, the post is not saved into the database for some reason.
Any idea why this is happening?
As I can see from your code you use
self.object = form.save(commit=False)
which mean that object will not be saved to database, but you can use it for futher processing. So you should use something like this:
self.object = form.save(commit=False) # Not hit database
self.object.user = self.request.user # Update user
self.object.post_date = datetime.now() # Update post_date
self.object.save() # And finally save your object to database.
Try this.
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
return super(PostCreate, self).form_valid(form)

Categories