I had a Product object in models,
from django.db import models
class Product(models.Model):
title = models.CharField(max_length=255, unique = True)
description = models.TextField()
image_url = models.URLField(verify_exists=True, max_length=200, blank = True, null = True)
quantity = models.PositiveSmallIntegerField(default=0)
def sell(self, save=True):
self.quantity -= 1
if save:
self.save()
and a template called:product_view.html Product {{product.title}}
and a template called:product_list.html {% for p in products %}{{p.id}},{% endfor %}
I want to make the view retrieve the Product object from the database, use it to render the template and finally return an HttpResponse object that contains the resulting string. If the Product with the given product_id can not be found raise a 404 exception (or return HttpResponseNotFound)
def productview(request, product_id):
"""
I dont know how to write here
"""
#render template "product_view.html" with param product
return HttpResponse("product %s" % product_id)
Meanwhile,if i want to render a page with a list of all available products. A product is available if it has a quantity that's bigger than 0. The template product_list.html is expecting a single parameter products in the context which refers to an iterable of Product objects.
So how to make the view retrieve the the available products and how to use them to render the template and return an HttpResponse object that contains the resulting string?
def available_products(request):
"""
I dont know how to write here
"""
#render template "product_list.html" with param products
return HttpResponse("View not implemented!")
Thx very much
have you tried with generic views?
class ProductDetailView(DetailView):
model = Product
template_name = 'product_view.html'
class ProductListView(ListView):
model = Product
template_name = 'product_list.html'
def get_queryset(self):
"""
Here you can filter the product list
"""
query_set = super(ProductListView, self).get_queryset()
return query_set.filter(quantity__gt=0)
#views.py
from django.shortcuts import render_to_response
from .models import Product
def productview(request):
"""
Retrive all products
"""
products = Product.objects.all()
return render_to_response('product_list.html',{'products': products})
def available_products(request):
"""
Retrive available products
"""
products = Product.objects.filter(quantity__gt=0)
return render_to_response('product_list.html',{'products': products})
These two also can be combined in one view, depending on defined url pattern.
For example:
#urls.py
urlpatterns = patterns('',
url(r'^product/(?P<which>\w+)/$', 'app_label.views.product_view'),
)
#views.py
def product_view(request, which):
"""
Retrive available products
"""
if which == 'all':
products = Product.objects.all()
else:
products = Product.objects.filter(quantity__gt=0)
return render_to_response('product_list.html',{'products': products})
Related
I'm new to Django/Django REST FW (and new to this community). I've spent a lot of time reading the documentation but I'm spinning my wheels at this point. I apologize in advance for being so long-winded here.
My back end DB is Postgres. I've got 3 Models, User, Item and ShoppingList. I need Item to contain a description (the item_name field) and its location. The user will select an item and add it to the ShoppingList for that day. The idea is that the user will then "check off" the item once acquired and it'll be "removed" from the view of the ShoppingList.
Here is where I'm having trouble: I don't want to duplicate the item_name and item_location fields in the shopping_list table, but I need to display those fields in the view of the shopping list (shopping_lists.py).
There is a one-to-many relationship between Item and ShoppingList respectively. The Item object is considered a "master items table" that stores descriptions and locations for each item. The ShoppingList object holds a temporary list of these "master items". I need a queryset that contains all fields from ShoppingList and 2 or more fields from Item.
I think this would be what Django REST FW considers a Reverse Relationship. I've tried a variety of changes to my Serialzer(s) and Models (including adding the Item Serializer to the ShoppingList Serializer) and gotten a variety of errors.
models/item.py:
from django.db import models
from django.contrib.auth import get_user_model
class Item(models.Model):
item_name = models.CharField(max_length=50, db_index=True)
item_location = models.CharField(max_length=10, blank=True, db_index=True)
item_class = models.CharField(max_length=20, blank=True)
# This is a relationship with User model
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
def __str__(self):
return f"item_name: {self.item_name}, item_location: {self.item_location}, shopper_id: {self.shopper_id}"
models/shopping_list.py:
from django.db import models
from django.contrib.auth import get_user_model
from .item import Item
class ShoppingList(models.Model):
item_num = models.ForeignKey(
'Item',
on_delete=models.DO_NOTHING # we don't want to delete the item from the "master" item list, just from this shopping list
)
# This is a relationship with user model.
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE # ...but we do want to delete the item if the user goes away as items are user-specific
)
item_qty = models.PositiveIntegerField()
item_complete = models.BooleanField(default=False)
added_on = models.DateField(auto_now=True)
# setting list_num to blank=True for this version
list_num = models.PositiveIntegerField(blank=True)
def __str__(self):
return f"item_num: {self.item_num}, shopper_id: {self.shopper_id}, item_qty: {self.item_qty}, item_complete: {self.item_complete}"
serializers/item.py:
from rest_framework import serializers
from ..models.item import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'item_name', 'item_location', 'item_class', 'shopper_id')
serializers/shopping_list.py:
from rest_framework import serializers
from ..models.shopping_list import ShoppingList
class ShoppingListSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingList
fields = ('id', 'item_num', 'shopper_id', 'item_qty', 'item_complete', 'added_on', 'list_num')
Getting ERROR AttributeError: Manager isn't accessible via ShoppingList instances when I execute the GET method in class ShoppingListItemView in views/shopping_lists.py below:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from ..models.shopping_list import ShoppingList
from ..serializers.shopping_list import ShoppingListSerializer
from ..models.item import Item
from ..serializers.item import ItemSerializer
class ShoppingListsView(APIView):
def get(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
data = ShoppingListSerializer(shopping_list_items, many=True).data
return Response(data)
def post(self, request):
request.data['shopper_id'] = request.user.id
list_item = ShoppingListSerializer(data=request.data, partial=True)
if list_item.is_valid():
list_item.save()
return Response(list_item.data, status=status.HTTP_201_CREATED)
else:
return Response(list_item.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
response_data = shopping_list_items.delete()
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class ShoppingListsAllView(APIView):
def get(self, request):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
data = ShoppingListSerializer(shopping_items, many=True).data
return Response(data)
class ShoppingListItemView(APIView):
def get(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_entry = list_item.objects.select_related('Item').get(id=pk)
print(list_entry)
data = ShoppingListSerializer(list_item).data
return Response(data)
def delete(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
request.data['shopper_id'] = request.user.id
updated_list_item = ShoppingListSerializer(list_item, data=request.data, partial=True)
if updated_list_item.is_valid():
updated_list_item.save()
return Response(updated_list_item.data)
else:
return Response(updated_item.errors, status=status.HTTP_400_BAD_REQUEST)
if you want to display only a few properties of an item in your ShoppingList you can use the SerializerMethodField method in your serializer
this would work as -
class ShoppingListSerializer(serializers.ModelSerializer):
itemProperty1 = serializers.SerializerMethodField()
itemProperty2 = serializers.SerializerMethodField()
class Meta:
model = ShoppingList
fields = ('id', "itemProperty1", "itemProperty2", 'more_fields')
def get_itemProperty1(self, instance):
return instance.item.anyPropertyOfItem if instance.item else ''
def get_itemProperty2(self, instance):
return instance.item.anyPropertyOfItem if instance.item.else ''
anyPropertyOfItem can be anything from item models.
Setting your serializer this way, your ShoppingList view will automatically show 2 new fields.
or you can also define read only fields with the help of #property in models to get the required field.
If you want to display all the properties of the item in the ShoppingList view, you can write here, will edit my answer. There you need to use related_name and get the item serializer in Shoppinglist serializer as the extra field.
For reverse relationship you should use related_name when defining the model or using the suffix _set.
The related_name attribute specifies the name of the reverse relation
from the User model back to your model. If you don't specify a
related_name, Django automatically creates one using the name of your
model with the suffix _set
Copied from What is related_name used for? by Wogan
AIM
I wish to render results.html with all the locations associated with the search_text entered by the User.
To do so, I am attempting to filter the results in get_quersey(), but am struggling to set the form_input variable. I am currently using form_input = self.request.GET.get('search_text').
CODE
models.py
import re
from django.db import models
from twython import Twython
class Location(models.Model):
""" Model representing a Location (which is attached to Hashtag objects
through a M2M relationship) """
name = models.CharField(max_length=1400)
def __str__(self):
return self.name
class Hashtag(models.Model):
""" Model representing a specific Hashtag serch by user """
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.ManyToManyField(Location, blank=True)
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations attached to the Hashtag model """
return list(self.locations.values_list('name', flat=True).all())
forms.py
from django import forms
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _
from .models import Location, Hashtag
class SearchHashtagForm(ModelForm):
''' ModelForm for user to search by hashtag '''
def clean_hashtag(self):
data = self.cleaned_data['search_text']
# Check search_query doesn't include '#'. If so, remove it.
if data[0] == '#':
data = data[1:]
return data
class Meta:
model = Hashtag
fields = ['search_text',]
labels = {'search_text':_('Hashtag Search'), }
help_texts = { 'search_text': _('Enter a hashtag to search.'), }
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from django.views.generic.edit import FormView
from .models import Location, Hashtag
from .forms import SearchHashtagForm
class HashtagSearch(FormView):
""" FormView for user to enter hashtag search query """
template_name = 'mapping_twitter/hashtag_search_form.html'
form_class = SearchHashtagForm
def get_success_url(self):
return reverse('mapping_twitter:results')
def form_valid(self, form):
# the method is called when valid form data has been POSTed, and returns an HttpResponse
form.clean_hashtag()
form.save()
return super().form_valid(form)
def form_invalid(self, form):
# Check if the search_text is invalid because it already exists in the database. If so, render results.html with that search_text
search_text = self.request.POST.get('search_text')
if search_text and Hashtag.objects.filter(pk=search_text).exists():
return HttpResponseRedirect(reverse('mapping_twitter:results'))
else:
return super(HashtagSearch, self).form_invalid(form)
class SearchResultsView(generic.ListView):
""" Generic class-based view listing search results of locations """
model = Hashtag
template_name = 'mapping_twitter/results.html'
def get_queryset(self, **kwargs):
# ISSUE: restrict the search_text displayed on results.html to the 'search_text' entered by the user
qs = Hashtag.objects.all()
form_input = self.request.GET.get('search_text')
if form_input:
qs = qs.filter(search_text__iexact=form_input)
return qs
def get_context_data(self, **kwargs):
context = super(SearchResultsView, self).get_context_data(**kwargs)
context['search_text'] = Hashtag.objects.all()
return context
In get_context_data you don't use get_queryset method at all. You just write another query which fetch all Hashtag objects. You should rewrite it to this:
def get_context_data(self, **kwargs):
context = super(SearchResultsView, self).get_context_data(**kwargs)
context['search_text'] = self.get_queryset()
return context
Note self.get_queryset() is using instead of Hashtag.objects.all().
Also note to use self.request.GET.get('search_text') you need to pass search_text as GET argument when performing redirect in HashtagSearch view:
class HashtagSearch(FormView):
""" FormView for user to enter hashtag search query """
template_name = 'mapping_twitter/hashtag_search_form.html'
form_class = SearchHashtagForm
def get_success_url(self):
return '{}?search_text={}'.format(reverse('mapping_twitter:results'), self.request.POST.get('search_text'))
UPD
But isntead of two views I recommend you to use single ListView. Just add GET form to your results.html and simple override get_queryset method:
# template
<form method="GET" action="">
<input type="text" name="search_text" placeholder="Search post" value="{{ request.GET.search_text }}">
<input type="submit" value="Search">
</form>
# view
class SearchResultsView(generic.ListView):
""" Generic class-based view listing search results of locations """
model = Hashtag
template_name = 'mapping_twitter/results.html'
def get_queryset(self, **kwargs):
# ISSUE: restrict the search_text displayed on results.html to the 'search_text' entered by the user
qs = Hashtag.objects.all()
form_input = self.request.GET.get('search_text')
if form_input:
qs = qs.filter(search_text__iexact=form_input)
return qs
EXPECTED OUTPUT
Assuming that the user enters "anaconda" in the Form:
Hashtag Search Results
You searched for: anaconda
ACTUAL OUTPUT
Hashtag Search Results
1) You searched for: <QuerySet [<Hashtag: >, <Hashtag: anaconda>]>
CODE
models.py
from django.db import models
class Location(models.Model):
""" Model representing a Location, attached to Hashtag objects through a
M2M relationship """
name = models.CharField(max_length=1400)
def __str__(self):
return self.name
class Hashtag(models.Model):
""" Model representing a specific Hashtag serch by user """
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.ManyToManyField(Location, blank=True)
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# Return a list of location names attached to the Hashtag model
return self.locations.values_list('name', flat=True).all()
forms.py
from django import forms
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _
from .models import Location, Hashtag
class SearchHashtagForm(ModelForm):
""" ModelForm for user to search by hashtag """
def clean_hashtag(self):
data = self.cleaned_data['search_text']
# Check search_query doesn't include '#'. If so, remove it.
if data[0] == '#':
data = data[1:]
# return the cleaned data
return data
class Meta:
model = Hashtag
fields = ['search_text',]
labels = {'search_text':_('Hashtag Search'), }
help_texts = { 'search_text': _('Enter a hashtag to search.'), }
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Location, Hashtag
from .forms import SearchHashtagForm
def hashtag_search_index(request):
""" View function for user to enter search query """
# If POST, process Form data
if request.method == 'POST':
# Create a form instance and populate it with data from request (binding):
form = SearchHashtagForm(request.POST)
# Check if form is valid
if form.is_valid():
search_text = form.cleaned_data['search_text']
form.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('mapping_twitter:results'))
# If GET (or any other method), create the default form
else:
form = SearchHashtagForm()
context = {'form':form, 'search_text':Hashtag.search_text}
return render(request, 'mapping_twitter/hashtag_search_index.html', context)
class SearchResultsView(generic.ListView):
""" Generic class-based view listing search results of locations """
model = Hashtag
template_name = 'mapping_twitter/results.html'
def get_queryset(self, **kwargs):
qs = super(SearchResultsView, self).get_queryset()
return qs
def get_context_data(self, **kwargs):
context = super(SearchResultsView, self).get_context_data(**kwargs)
context['search_text'] = Hashtag.objects.all()
return context
results.html
<h1>Hashtag Search Results</h1>
<p>1) You searched for: {{ search_text }}</p>
{% for obj in queryset %}
<p>2) You searched for: {{ obj.search_text }}</p>
{% endfor %}
I think you should pass the queryset of the Model HashTag as you need to render out hashtag.search_text which is an element of that model.
So you can pass the queryset in the view and for loop through it and print all the search_text or pass the object alone and render out its search_text.
context = {
'queryset': qs
}
{% for obj in queryset %}
<p>{{ obj.search_text }}</p>
{% endfor %}
This is my guess, correct me if I am wrong.
For a template to display something, you have to send context or data to that template to print, how can it print something when you don't send anything. So,
context.update({
'hashtag': //value you want to send.
})
Im trying to make a url setup that would display a detail page based on just a year (not month and day), and a pk. like this /YEAR/ISSUE/
This is what I have tried:
My model.py:
class Mag(models.Model):
name = models.CharField(max_length=500)
issue = models.PositiveIntegerField()
pub_date = models.DateTimeField()
unique_together = ("pub_date", "issue")
def __unicode__(self):
return self.name
#models.permalink
def get_absolute_url(self):
creation_date = timezone.localtime(self.pub_date)
return ('mag_detail', (), {
'year': creation_date.strftime('%Y'),
'pk': self.issue})
My views.py:
class MagDetail(DateDetailView):
model = Mag
pk_url_kwarg='pk'
date_field='pub_date'
My urls.py
urlpatterns = patterns('',
url(r'^(?P<year>\d{4})/(?P<pk>\d+)/$', MagDetail.as_view(), name='mag_detail'),
)
But when I try a url like 2014/1, I get an error that month is not specified.
Is it possible to do what I want with DateDetailView, or do I need to look another class?
What if you use a standard DetailView and override the get_object() method, like this:
from django.shortcuts import get_object_or_404
class MagDetail(DetailView):
model = Mag
def get_object(self):
obj = get_object_or_404(
self.model,
pk=self.kwargs['pk'],
pub_date__year=self.kwargs['year'])
return obj
The DateDetailView expects the format of year/month/day
As a general approach, if you will be using this kind of view in your project for other models than Mag alone, you can derive from it though and change the get_object method, roughly like that:
class YearDetailView(DateDetailView):
def get_object(self, queryset=None):
"""
Get the object this request displays.
"""
year = self.get_year()
# Use a custom queryset if provided
qs = queryset or self.get_queryset()
if not self.get_allow_future() and int(year) > datetime.today().year:
raise Http404(_("Future %(verbose_name_plural)s not available because
%(class_name)s.allow_future is False.") % {
'verbose_name_plural': qs.model._meta.verbose_name_plural,
'class_name': self.__class__.__name__,
})
lookup_args = {"%s__year" % self.get_date_field(): year}
qs = qs.filter(**lookup_args)
return super(BaseDetailView, self).get_object(queryset=qs)
class MagDetail(YearDetailView): # note that we derive from our new class!
model = Mag
pk_url_kwarg='pk'
date_field='pub_date'
I have a ManyToMany field with a relationship model. I want a formset, filtered on one of the
keys, which shows a form for each of the other keys.
My guess is that a custom manager on the relationship model is the key to solving this problem. The manager would return "phantom" instances initialized with the appropriate ForeignKey when no real instance was in the database. I'm just don't know how to make a manager add "phantom" instances when it seems designed to filter out existing ones.
I'm hoping an example is worth 1K words.
Say I want my users to be able to rate albums. I would like to display a formset with
a form for all albums by the selected band. Example models & view
from django.contrib.auth.models import User
from django.db import models
class Band(models.Model):
name = models.CharField(max_length=30)
def __unicode__(self):
return self.name
class Album(models.Model):
name = models.CharField(max_length=30)
band = models.ForeignKey(Band)
ratings = models.ManyToManyField(User, through="Rating")
def __unicode__(self):
return self.name
class Rating(models.Model):
user = models.ForeignKey(User)
album = models.ForeignKey(Album)
rating = models.IntegerField()
def __unicode__(self):
return "%s: %s" % (self.user, self.album)
# views.py
from django.forms.models import modelformset_factory
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from models import Band, Rating
RatingFormSet = modelformset_factory(Rating, exclude=('user',), extra=1)
def update(request):
user = request.user
band = Band.objects.all()[0]
formset = RatingFormSet(request.POST or None,
queryset=Rating.objects.filter(album__band=band,
user=user))
if formset.is_valid():
objects = formset.save(commit=False)
print "saving %d objects" % len(objects)
for obj in objects:
obj.user = user
obj.save()
return HttpResponseRedirect("/update/")
return render_to_response("rating/update.html",
{'formset': formset, 'band':band},
context_instance=RequestContext(request))
The problem is it only shows forms for existing relationship instances. How can I get an entry for all albums.
Thanks.
I came back to this problem after again searching the web. My intuition that a custom manager was required was wrong. What I needed was a custom inline formset that takes two querysets: one to search and the other one with an ordered list of items to be displayed.
The problem with this technique is that model_formsets really like to have existing instances followed by extra instances. The solution is two make two lists of instances to display: existing records and extra records. Then, after model_formsets creates the forms, sort them back into display order.
To sort the formset forms, you need to apply my django patch [14655] to make formsets iterable & then create a sorting iterator.
The resulting view is shown below:
from django.contrib.auth.models import User
from django.forms.models import inlineformset_factory, BaseInlineFormSet, \
BaseModelFormSet, _get_foreign_key
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from models import Band, Rating
class list_qs(list):
"""a list pretending to be a queryset"""
def __init__(self, queryset):
self.qs = queryset
def __getattr__(self, attr):
return getattr(self.qs, attr)
class BaseSparseInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.display_set = kwargs.pop('display_set')
self.instance_class = kwargs.pop('instance_class', self.model)
# extra is limited by max_num in baseformset
self.max_num = self.extra = len(self.display_set)
super(BaseSparseInlineFormSet, self).__init__(*args, **kwargs)
def __iter__(self):
if not hasattr(self, '_display_order'):
order = [(i, obj._display_order)
for i, obj in enumerate(self._instances)]
order.sort(cmp=lambda x,y: x[1]-y[1])
self._display_order = [i[0] for i in order]
for i in self._display_order:
yield self.forms[i]
def get_queryset(self):
if not hasattr(self, '_queryset'):
# generate a list of instances to display & note order
existing = list_qs(self.queryset)
extra = []
dk = _get_foreign_key(self.display_set.model, self.model)
for i, item in enumerate(self.display_set):
params = {dk.name: item, self.fk.name: self.instance}
try:
obj = self.queryset.get(**params)
existing.append(obj)
except self.model.DoesNotExist:
obj = self.instance_class(**params)
extra.append(obj)
obj._display_order = i
self._instances = existing + extra
self._queryset = existing
return self._queryset
def _construct_form(self, i, **kwargs):
# make sure "extra" forms have an instance
if not hasattr(self, '_instances'):
self.get_queryset()
kwargs['instance'] = self._instances[i]
return super(BaseSparseInlineFormSet, self)._construct_form(i, **kwargs)
RatingFormSet = inlineformset_factory(User, Rating, formset=BaseSparseInlineFormSet)
def update(request):
band = Band.objects.all()[0]
formset = RatingFormSet(request.POST or None,
display_set=band.album_set.all(),
instance=request.user)
if formset.is_valid():
objects = formset.save(commit=False)
print "saving %d objects" % len(objects)
for obj in objects:
obj.save()
return HttpResponseRedirect("/update/")
return render_to_response("rating/update.html",
{'formset': formset, 'band':band},
context_instance=RequestContext(request))