In Django I have my app where I place information about countries and cities of these countries. This is my model.py file:
class Country(models.Model):
class Meta:
verbose_name_plural = u'Countries'
name = models.CharField(max_length=50)
slug = models.CharField(max_length=255)
description = models.TextField(max_length=10000, blank=True)
def __unicode__(self):
return self.name
class City(models.Model):
class Meta:
verbose_name_plural = u'Cities'
name = models.CharField(u'city', max_length=200)
slug = models.CharField(max_length=255, blank=True)
description = models.TextField(max_length=10000, blank=True)
country = models.ForeignKey('Country', blank=True, null=True)
def __unicode__(self):
return self.name
I have the detail view of my country, in this view there is a list of cities of this country(views.py):
def CountryDetail(request, slug):
country = get_object_or_404(Country, slug=slug)
list_cities = City.objects.filter(country=country)
return render(request, 'country/country.html', {'country':country, 'list_cities':list_cities})
this is my urls.py:
url(r'^(?P<slug>[-_\w]+)/$', views.CountryDetail, name='country'),
I want to create a url of cities which contain a slug of the country and a slug of the city, for example domain.com/spain/barcelona/.
So I created the detail view of the city, and it's looks like this:
def CityDetail(request, cityslug, countryslug):
country = Country.objects.get(slug=countryslug)
country_city = City.objects.get(country=country)
city = get_object_or_404(country_city, slug=cityslug)
return render(request, 'country/city.html', {'country':country, 'city':city})
Here is my urls.py for city detail:
url(r'^(?P<countryslug>[-_\w]+)/(?P<cityslug>[-_\w]+)$', views.CityDetail, name='resort'),
And this is how it looks like in my html file detail of the country that links to the cities:
<h1>{{country.name}}</h1>
<p>{{country.description}}</p>
<h2>Cities</h2>
{% for city in list_cities %}
<a href="/{{country.slug}}/{{city.slug}}">
<p>{{city.name}}</p>
</a>
{% endfor %}
But when I click on the link of the url of the city, I get error.
Object is of type 'City', but must be a Django Model, Manager, or QuerySet
In traceback i see that problem in CityDetail function in views.py file:
resort = get_object_or_404(country_city, slug=cityslug).
Hope you can help me with its problem. Thank You.
Yes, you can't call get_object_or_404 on an actual City object; the things that you can query are models or querysets, not instances. What do you think that call would do?
But actually the underlying problem is before that; your definition of country_city makes no sense. For a start, this is only currently working because you presumably have only one City in the country you're querying. When you have more than one, that line will fail with a MultipleObjectsReturned exception, because get can only return a single object.
You should remove that line altogether, and query the city directly from the country:
country = Country.objects.get(slug=countryslug)
city = get_object_or_404(City, country=country, slug=cityslug)
Related
I am working on a product overview page in Django.
For this I have three models: category, brand, product.
I have created a View with ListView of the category. I loop through them to display them. I then want to open another overview of all brands within that category.
How do I do this?
Here are my models:
class Category(models.Model):
category_name = models.CharField(max_length=200)
sub_category = models.CharField(max_length=200,blank=True,null=True)
category_picture = models.ImageField(upload_to='category/', null=True, blank=True)
def __str__(self):
if self.sub_category is None:
return self.category_name
else:
return f" {self.category_name} {self.sub_category}"
class Meta:
ordering = ['category_name']
class Brand(models.Model):
category = models.ForeignKey('Category', on_delete=models.SET_NULL,null=True,blank=True)
brand_name = models.CharField(max_length=200)
brand_owner = models.CharField(max_length=200)
brand_story = models.TextField()
brand_country = models.CharField(max_length=200)
def __str__(self):
return f"{self.brand_name}"
class Bottle(models.Model):
category_name = models.ForeignKey('Category', on_delete=models.SET_NULL,null=True,blank=True)
brand = models.ForeignKey('Brand', on_delete=models.CASCADE)
bottle_name = models.CharField(max_length=255)
bottle_info = models.TextField()
bottle_tasting_notes = models.TextField()
bottle_barcode = models.IntegerField()
bottle_image = models.ImageField(upload_to='bottles/',null=True)
def __str__(self):
return f"{self.brand.brand_name} {self.bottle_name}"
How do I open a listview of all brands within a certain category from a link of the category listview?
First thing is to create another listview that displays all brands within a specific category. We do this by creating a new get_queryset() method.
views.py
class BrandListView(ListView):
model = Brand
def get_queryset(self):
return Brand.objects.filter(category__category_name=self.kwargs['category'])
Next add a url to your URLS.py so it can be accessed. We're using category_name as part of the url so it's human readable
from .views import BrandListView
urlpatterns = [
path('brands/<str:category>/', PublisherBookList.as_view()), name= "brand_list"
]
Then, as you loop through your categories in your template, create a link to the url
{% for category in categories %}
{{category.category_name}} : See brands in category
{% endfor %}
This will work as long as your categories have fairly simple names. If not, you might want to use the ID in the URL instead, or add a slug field to the model and use that.
I have a profile, and in this profile I want to display bookmarks for all messages (this is my IntegerField). In other words, how many people have bookmarked a particular author's posts.
models.py
class Post(models.Model):
slug = models.SlugField(unique=True)
title = models.CharField(max_length=255, db_index=True)
author = models.ForeignKey(
"users.CustomUser", on_delete=models.SET_NULL, null=True, db_index=True
)
bookmarkscount = models.IntegerField(null=True, blank=True, default=0)
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
This is my try in template but it does not work
<p>Bookmark</p>
<p>{{posts.bookmarkscount}}</p>
But work only if I use "for"
{% for post in posts %}
<p>{{ post.bookmarkscount}}</p>
{% endfor %}
views.py
class ProfileDetailView(DetailView):
model = Profile
template_name = "users/profile/profile.html"
def get_context_data(self, **kwargs):
try:
context["posts"] = Post.objects.filter(
author=self.object.user.is_authenticated
)
except Post.DoesNotExist:
context["posts"] = None
posts is a QuerySet type, a representation of the query to be sent to the DB. More like a list on steroids rather than a single instance of Post. This is a crucial concept you need to understand before coding anything in Django. (Docs here)
In order to get a sum of all the bookmarkscount values from all posts of a user, you need to use aggregation. (Docs here)
from django.db.models import Sum
posts.aggregate(Sum('bookmarkscount'))
# returns i.e.: {'bookmarkscount': 234}
Hi everyone I am new at Django and working on e-commerce site. I create a model name category and pass it to model shop by using foreign key. In Category model I have category Sale and i want to fetch all products that have category sale in my landing page and rest of us in shop page. Any one please help me how I do it?
My model.py code is:
class category(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class shop(models.Model):
s_id = models.AutoField(primary_key=True)
s_name = models.CharField(max_length=50)
s_category = models.ForeignKey(category, on_delete= models.CASCADE)
s_artical_no = models.IntegerField(default=0)
View.py:
def index(request):
prod = shop.objects.get(s_category = 4)
params = {'prod': prod}
return render(request, "main/index.html", params )
Use related_name to accomplish this
class shop(models.Model):
s_category = models.ForeignKey(category, on_delete= models.CASCADE, related_name='shop_list')
In views.py
def index(request):
specific_category = category.objects.get(id=4)
prod = category.shop_list.all() #use related_name here
params = {'prod': prod}
return render(request, "main/index.html", params )
Hint: your classes names should follow the UpperCaseCamelCase convention so it should be Shop, Category
s_category is a category instance, you can't pass a category id. First get the category object for "Sale" i.e.
sale_category = category.objects.get(pk=4)
then simply use
prod = sale_category.shop_set.all()
As a side note, try to use PEP-8 compliant class names. The way you name your classes is confusing
I need to join and pass two queries of category and subcategory as elements of an autocomplete select2 dropdown as my django form field as below:
This is my form:
class CategoriesAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Categories.objects.none()
qs = Categories.objects.all()
if self.q:
qs = qs.filter(Q(name__icontains=self.q))
return qs
def get_result_label(self, item):
return format_html( item.name)
class categories_form(forms.ModelForm):
categories = forms.ModelChoiceField(
queryset= Categories.objects.none(),
widget= autocomplete.ModelSelect2(
url='load_categories',
attrs={
'data-placeholder': 'Select a category',
'data-html': True,
'style': 'min-width: 15em !important;',
}
)
)
class Meta:
model = Post
fields = ['categories']
def __init__(self, *args, **kwargs):
super(category_form, self).__init__(*args, **kwargs)
self.fields['categories'].queryset = Categories.objects.all()
and in url:
path('ajax/categories-autocomplete', CategoriesAutocomplete.as_view(), name='load_categories'),
for category model:
class Categories(models.Model):
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
class Meta:
db_table = "categories"
for sub-category model:
class Sub_Categories(models.Model):
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
class Meta:
db_table = "sub_categories"
model to connect category with sub-categories:
class Categories_Sub_Categories(models.Model):
category = models.OneToOneField(Categories, primary_key=True, on_delete=models.CASCADE)
sub_cat = models.OneToOneField(Sub_Categories, on_delete=models.CASCADE)
class Meta:
db_table = "categories_sub-categories"
and in Post model:
class Post(models.Model):
title = models.CharField(max_length=50)
descript = HTMLField(blank=True, null=True)
categories = models.ForeignKey(Categories, blank=True, null=True, on_delete=models.CASCADE)
subcategories = models.ForeignKey(Sub_Categories, blank=True, null=True, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateTimeField(default=timezone.now)
UPDATE:
The answer provided by #ha-neul is working just there is a bug. I show it with an example:
This is what expect in the dropdown:
**Asia**
China
Malaysia
India
Tajikistan
Iran
Qatar
**Europe**
Germany
Italy
Spain
Netherlands
France
**Africa**
Gana
...
But this is what I see:
**Asia**
China
Malaysia
**Europe**
Netherlands
France
Sweden
Norway
**Asia**
India
Tajikistan
Iran
**Europe**
Germany
Italy
**Asia**
Qatar
**Africa**
Gana
...
**America**
....
**Europe**
Spain
in the SubCategory table I have something like:
id ........... category_id
1 1
2 1
3 1
4 1
5 3
6 1
7 2
I am following this package. Any idea to make me even closer to the solution would be appreciated!!
If this is what you want to achieve, then:
The short answer is you should
have your subcategory with ForeignKeyField referring to
Category model.
use Select2GroupQuerySetView instead of Select2QuerySetView.
But implementing it is a bit complicated.
First of all, although django-autocomplete-light's source code has Select2GroupQuerySetView , somehow you cannot just use it as autocomplete.Select2GroupQuerySetView. So, you have to write the same thing in your own views.py. In addition, the source code has a typo, so you need to fix it.
Step 1. In models.py:
class Category(models.Model):
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
class SubCategory(models.Model):
##################
#You need add this line, so there is a one-to-many relationship
category = models.ForeignKey(Category, on_delete=models.CASCADE,
related_name='subcategories')
###############
name = models.CharField(max_length=100)
brief = models.TextField(null=True)
slug = models.SlugField(max_length=200)
date = models.DateTimeField(default=timezone.now)
Step2.in views.py copy-paste Select2GroupQuerySetView code and fix a typo
# import collections, so Select2GroupQuerySetView can work
import collections
class Select2GroupQuerySetView(autocomplete.Select2QuerySetView):
group_by_related = None
related_field_name = 'name'
def get_results(self, context):
if not self.group_by_related:
raise ImproperlyConfigured("Missing group_by_related.")
groups = collections.OrderedDict()
object_list = context['object_list']
print(object_list)
object_list = object_list.annotate(
group_name=F(f'{self.group_by_related}__{self.related_field_name}'))
for result in object_list:
group_name = getattr(result, 'group_name')
groups.setdefault(group_name, [])
groups[group_name].append(result)
return [{
'id': None,
'text': group,
'children': [{
'id': result.id,
'text': getattr(result, self.related_field_name),
# this is the line I had to comment out
#'title': result.descricao
} for result in results]
} for group, results in groups.items()]
3. write your own view using Select2GroupQuerySetView
class SubCategoryAutocomplete(Select2GroupQuerySetView):
print('under subcategory autocomplete')
group_by_related = 'category' # this is the fieldname of ForeignKey
related_field_name = 'name' # this is the fieldname that you want to show.
def get_queryset(self):
##### Here is what you normally put... I am showing the minimum code.
qs = SubCategory.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
Howe to use this view in a your project?
1. Say you have a Post model as below, with subcategory as a ForeignKey.
class Post(models.Model):
title = models.CharField(max_length=100)
subcategory = models.ForeignKey(SubCategory,on_delete=models.CASCADE)
def __str__(self):
return self.title
2. You will generate a PostForm that contains the subcategory autocomplete field.
in forms.py
from django.conf.urls import url
class PostForm(forms.ModelForm):
subcategory = forms.ModelChoiceField(
queryset=Subcategory.objects.all(),
widget=autocomplete.ModelSelect2(url='subcategory-autocomplete')
)
class Meta:
model= Post
fields = ('title','subcategory')
You will generate a CreatePostView using generic CreateView
from django.views.generic.edit import CreateView
class CreatePostView(CreateView):
model=Post
template_name='yourapp/yourtemplate.html'# need to change
form_class=PostForm
success_url = '/'
Now, in your urls.py, one url for CreatePostView another one for autocomplete view.
urlpatterns = [
url(
r'^subcategory-autocomplete/$',
SubCategoryAutocomplete.as_view(),
name='subcategory-autocomplete',
),
path('post/create',CreatePostView.as_view(), name='create_post'),
it's all set, you will go to post/create and see a PostForm with subcategories autocomplete field.
OP had a weird grouping behavior after using the code above. In his comment, he mentioned:
Added a .order_by(category_id')` to the qs and fixed it.
You do not need to create this model to make a relation
Categories_Sub_Categories
just create a one-to-many field (categories) in Sub_Categories model and put Categories model there (foreign), it will do that automatically, then retrieve data like this (in backend)
categories = Categories.objects.all()
you will get all categories with Sub_Categories object here, pass it to frontend and loop through it (in front-end)
for category in categories:
sub_categories = category.sub_categories_set.all()
To make SubCategory you can have ForeignKey to self.
Another point, you'll need to use prefetch_related from main model (Post) to be able to "join" Category/SubCategory there.
Here is an example how this should look like:
# forms.py
from django import forms
from django.db.models import Prefetch
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [...]
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
cats = Category.objects \
.filter(category__isnull=True) \
.order_by('order') \
.prefetch_related(Prefetch('subcategories',
queryset=Category.objects.order_by('order')))
self.fields['subcategory'].choices = \
[("", self.fields['subcategory'].empty_label)] \
+ [(c.name, [
(self.fields['subcategory'].prepare_value(sc),
self.fields['subcategory'].label_from_instance(sc))
for sc in c.subcategories.all()
]) for c in cats]
# models.py
class Category(models.Model):
category = models.ForeignKey('self', null=True, on_delete=models.CASCADE,
related_name='subcategories', related_query_name='subcategory')
class Post(models.Model):
subcategory = models.ForeignKey(Category, on_delete=models.CASCADE,
related_name='posts', related_query_name='post')
I'm using Django 2.2 and PostgreSQL. I want to display the product information that the user has added to the detail page. I see the information in the 'StoreOtherInfo' model, but I don't see the information in the 'Product' model. How can I do that?
store/models.py
class StoreOtherInfo(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,blank=True, null=True)
phone = models.CharField(max_length=11)
fax = models.CharField(max_length=11)
province = models.CharField(max_length=11)
district = models.CharField(max_length=11)
neighborhood = models.CharField(max_length=11)
def __str__(self):
return self.user.username
products/models.py
class Product(models.Model):
seller = models.ForeignKey(User, on_delete = models.CASCADE)
product_name = models.CharField(max_length = 50)
description = RichTextField()
added_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.seller
store/views.py
from store.models import StoreOtherInfo
from products.models import Product
def neighbor_detail(request,username):
neighbor_detail = get_object_or_404(User,username = username)
neighbor_list = StoreOtherInfo.objects.all()
product_list = Product.objects.all()
return render(request, 'store/neighbor_detail.html', {'neighbor_detail':neighbor_detail, 'neighbor_list':neighbor_list, 'product_list':product_list})
templates/neighbor_detail.html
<strong><p>{{neighbor_detail.first_name}} {{neighbor_detail.last_name}}</p></strong>
<p>{{neighbor_detail.username}}</p>
<p>{{neighbor_detail.email}}</p>
<p>{{neighbor_detail.storeotherinfo.phone}}</p>
<p>{{neighbor_detail.storeotherinfo.fax}}</p>
<p>{{neighbor_detail.storeotherinfo.province}}</p>
<p>{{neighbor_detail.storeotherinfo.district}}</p>
<p>{{neighbor_detail.storeotherinfo.neighborhood}}</p>
<p>{{neighbor_detail.product.product_name}}</p>
<p>{{neighbor_detail.product.description}}</p>
<p>{{neighbor_detail.product.added_date}}</p>
according to your Product model:
seller = models.ForeignKey(User, on_delete = models.CASCADE)
the relation between User model and Product model is one-to-many. This means every User(in this case seller) can have multiple Products.
So when you try to access the Product objects of a User object as you did in you template: <p>{{neighbor_detail.product.product_name}}</p> you end up giving an attribution error because neighbor_detail.product is not a single object of Product class it's a collection.
replace your template code with this and i hope you realize whats happening.
<strong><p>{{neighbor_detail.first_name}} {{neighbor_detail.last_name}}</p></strong>
<p>{{neighbor_detail.username}}</p>
<p>{{neighbor_detail.email}}</p>
<p>{{neighbor_detail.storeotherinfo.phone}}</p>
<p>{{neighbor_detail.storeotherinfo.fax}}</p>
<p>{{neighbor_detail.storeotherinfo.province}}</p>
<p>{{neighbor_detail.storeotherinfo.district}}</p>
<p>{{neighbor_detail.storeotherinfo.neighborhood}}</p>
{% for product in neighbor_detail.product_set.all %}
{{product.product_name}}<br/>
{{product.description}}<br/>
{{product.added_date}}<br/>
{% endfor %}
note that product_set is the default name that django associate with products related to each user.
A User can have multiple Products, so you can't do neighbor_detail.product because product isn't defined on a User. You need to loop through the list of products with {% for product in neighbor_detail.product_set.all %} and then you can display the properties of each product.
Read [this] for more information about one-to-many relationships.