Hiding foreign key fields from ModelForms in Django Template - python

Essence in the following.
I have a few different Magazine. And AutoForm from template shows all Articles and all Authors for possible selection (not only from current Magazine). How to restrict this choice of author a for article only from the current magazine? Can I do this using only the Template? If not, then how?
models.py
class Magazine(models.Model):
Name = models.CharField(max_length=200)
class Article(models.Model):
Name = models.CharField(max_length=200)
Magazine = models.ForeignKey(Magazine)
class Author(models.Model):
Name = models.CharField(max_length=200)
Magazine = models.ForeignKey(Magazine)
class Sets(models.Model):
Name = models.CharField(max_length=200)
Magazine = models.ForeignKey(Magazine)
Aut = models.ManyToManyField(Author, null=True, blank=True)
Set = models.ForeignKey(Article, null=True, blank=True)
forms.py:
class MagazineForm(ModelForm):
class Meta:
model = Magazine
fields = {'Name'}
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = {'Name'}
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = {'Name'}
class SetsForm(ModelForm):
class Meta:
model = Sets
fields = {'Name', 'Set', 'Aut'}
views.py
def building_details(request, magazine_id):
sets_form = SetsForm
args = {}
args.update(csrf(request))
args['magazine'] = Magazine.objects.get(id=magazine_id)
args['article'] = Article.objects.filter(Magazine=magazine_id)
args['author'] = Author.objects.filter(Magazine=magazine_id)
args['sets'] = Sets.objects.filter(Magazine=magazine_id)
args['setform'] = sets_form
return render_to_response('building.html', args)
Template
<form action='/add/{{ magazine.id }}/' method='post'>
{% csrf_token %}
{{ setform }}
<input type='submit' class='button' value="Добавить">
</form>
</div>
Maybe somewhere in this entry there are errors (I briefly edited, removing not playing a role here, essentially). In General I have everything working right, except that all objects are displayed, instead of only having the current.

Sounds like you are trying to restrict the options in the ForeignKey selector based on some criteria.
This means you need to override some of the details in the forms __init__ method:
class SetsForm(ModelForm):
class Meta:
model = Sets
fields = {'Name', 'Set', 'Aut'}
def __init__(self, *args, **kwargs):
articles = Article.objects.filter(Magazine=kwargs.pop('magazine_id'))
super(SetsForm, self).__init__(*args, **kwargs)
self.fields['articles'] = forms.ModelChoiceField(
queryset=articles ,
label="Articles",
)
Then call your form like so:
sets_form = SetsForm(magazine_id=magazine_id)

To get authors of articles only in the current magazine:
articles = Article.objects.filter(Magazine=magazine_id)
sets = Sets.objects.filter(Magazine=magazine_id, Set__in=articles)
import itertools
# build a flat list of all Authors in these sets (and call `set` to avoid duplicates)
authors = set(itertools.chain(*[s.Aut.all() for s in sets]))
This would be easier if there were a direct foreign key on Author to Article.
Also, you should follow Django convention by using lowercase field names. This will avoid confusion between fields and models, which use uppercase.

Related

Django admin drop down with very long description/text

just like the pic in admin panel when creating some post:
that basically one of dropdown menu options, it is so long, is there any idea on how i can change it to be multi-line? or maybe change the drop down menu to a "select table", the admin in this case need to read the description, so it is unwise for it to be formatted like that.
I have a code example:
models.py
class CreatePost(models.Model):
subject = models.CharField(max_length=99)
desc = models.TextField(max_length=9000)
isSolved = models.BooleanField(default=False) # a button
user = models.ForeignKey(User, on_delete=models.CASCADE,related_name="user_post")
def __str__(self):
return format_html('SUBJECT : {} <br/> DESCRIPTION : {} <br/> Email : {} <br/> ',self.subject, self.desc, self.user.username)
# remember to show the name of ticket sender
class RespondToPost(models.Model):
ticket = models.ForeignKey(CreatePost,on_delete=models.CASCADE)
to = models.EmailField(max_length=320)
content = models.TextField()
def __str__(self):
return format_html('SUBJECT : {} <br/> DESCRIPTION : {} <br/> EMAIL : {} <br/> ',self.post.subject, self.post.desc, self.post.user.username)
admin.py
class CreatePostAdmin(admin.ModelAdmin):
list_display = ('id', '__str__')
class Meta:
model = models.CreatePost
class RespondToPostAdmin(admin.ModelAdmin):
list_display = ('id', '__str__', 'to', 'content')
class Meta:
model = models.RespondToPost
any idea?
You can add a custom field to list_display and change its content however you like:
from django.utils.text import Truncator # for shortening a text
class CreatePostAdmin(admin.ModelAdmin):
list_display = ('id', 'get_truncated_str')
class Meta:
model = models.CreatePost
def get_truncated_str(self, obj):
# Change it however you want, shortening is just an example
return Truncator(str(obj)).words(10)

How to fix trouble with urls better django

I have a section residential interiors on the site, so the typical url looks like this https://caparolcenterspb.ru/decision/livingrooms/kitchen/provans/ (room and style)
However different rooms may have the same styles and when searching for styles in views.py it may output several styles and an error
views.py
selected_room = Room.objects.get(slug=rmslg)
style = Style.objects.get(slug=stslg)
When you try to replace slug with different styles depending on the room(for example, provans_kitchen), an error occurs in the template(just put provans by default will not work)
residentialInteriors.html
{% for room in all_rooms %}
<li class="menu__item menu__item_interiors">{{ room.name }}</li>
{% endfor %}
I have 2 solution ideas(preferably 2):
either change stslg in template by default to 'provans_' + str(room. slug), but this line does not work(especially not the fact that provans will be everywhere)
either search for style in views.py not only for stslg, but also for rmslg, but for this in the style model, you need to create a room field inherited from the room model, which also does not work for me, since Room is declared further than Style
models.py
class Style(models.Model):
name = models.CharField(max_length=30)
full_name = models.CharField(max_length=50)
slug = models.SlugField()
img = models.ImageField(upload_to='img/')
walls = models.TextField()
floor = models.TextField()
roof = models.TextField()
def __str__(self):
return self.full_name
class Meta:
verbose_name = 'Стили'
verbose_name_plural = 'Стили'
class Room(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField()
styles = models.ManyToManyField(Style)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Комнаты'
verbose_name_plural = 'Комнаты'
Sounds like you want to use the relation field that you already have on Room
to only find styles associated with the given room?
selected_room = Room.objects.get(slug=rmslg)
style = selected_room.styles.get(slug=stslg)

django - CBV - pass in multiple values from url

I am utterly confused and mortified by CBVs, seeking for help.
So I've designed the model structure and decided the url patterns as following, but simply can't write a valid CBV to facilitate the urls:
models.py
class Category(models.Model):
'''Category for men's and women's items'''
men = models.BooleanField()
women = models.BooleanField()
name = models.CharField(max_length=100)
description = models.CharField(max_length=300, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta():
verbose_name_plural = 'Categories'
def __str__(self):
return ("Men's " + self.name) if self.men else ("Women's " + self.name)
class SubCategory(models.Model):
'''Sub-category for the categories (not mandatory)'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.CharField(max_length=300, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta():
verbose_name = 'Sub-category'
verbose_name_plural = 'Sub-categories'
def __str__(self):
return ("Men's " + self.name) if self.category.men else ("Women's " + self.name)
class Item(models.Model):
'''Each item represents a product'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
subcategory = models.ForeignKey(
SubCategory, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.IntegerField(default='0')
discount = models.IntegerField(null=True, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta:
ordering = ['-uploaded_date']
def __str__(self):
return self.name
def discounted_price(self):
'''to calculate the price after discount'''
return int(self.price * (100 - self.discount) * 0.01)
class ItemImage(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(upload_to='itemimages', null=True, blank=True)
urls.py
app_name = 'boutique'
urlpatterns = [
# show index page
path('', views.IndexView.as_view(), name='index'),
# show categories of products for men or women
path('<slug:gender>/', views.ItemListView.as_view(), name='show-all'),
# show a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/', views.ItemListView.as_view(), name='category'),
# show a specific subcategory under a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/subcat_<int:subcategory_pk>/', views.ItemListView.as_view(), name='subcategory'),
# show a specific item
path('item_<int:item_pk>/', views.ItemDetailView.as_view(), name='item'),
]
views.py
class IndexView(ListView):
'''landing page'''
model = Category
template_name = 'boutique/index.html'
context_object_name = 'categories'
class ItemListView(ListView):
'''display a list of items'''
# model = Category ??? what's the point of declaring model when get_context_data() ???
template_name = 'boutique/items.html'
context_object_name = 'categories'
paginate_by = 12
def get_object(self):
obj = get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all() # for rendering nav bar data
return context
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
context_object_name = 'item'
def get_object(self):
return get_object_or_404(Item, pk=self.kwargs['item_pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
items.html
<a href="{% url 'boutique:show-all' 'women' %}> link to categories of product for women </a>
<a href="{% url 'boutique:category' 'women' category.pk %}> link to a cat for women </a>
<a href="{% url 'boutique:subcategory' 'women' category.pk subcategory.pk %}> link to a subcat under a specific cat for women </a>
Essentially, as you can see, I'd like the ItemListView to render multiple url paths depending on what value passed into the CBV... I can do it (pass multiple values) in a FBV, however, utterly confused by the mechanism of the CBVs...
So if anyone could write an example ItemListView and according anchor url template tags (if mine are incorrect), it would be tremendulent!!! Thanks!!!
EDIT 1
class ItemListView(ListView):
'''display a list of items'''
model = Item
template_name = 'boutique/items.html'
# paginate_by = 12
def get_queryset(self):
# get original queryset: Item.objects.all()
qs = super().get_queryset()
# filter items: men/women
if self.kwargs['gender'] == 'women':
qs = qs.filter(category__women=True)
elif self.kwargs['gender'] == 'men':
qs = qs.filter(category__men=True)
if self.kwargs.get('category_pk'):
qs = qs.filter(category=self.kwargs.get('category_pk'))
if self.kwargs.get('subcategory_pk'):
qs = qs.filter(subcategory=self.kwargs.get('subcategory_pk'))
# print(qs)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
context['categories'] = Category.objects.all()
if self.kwargs.get('gender') == 'women':
context['category_shown'] = Category.objects.filter(women=True)
if self.kwargs.get('gender') == 'men':
context['category_shown'] = Category.objects.filter(men=True)
if self.kwargs.get('category_pk'):
context['category_shown']=get_object_or_404(Category, pk=self.kwargs.get('category_pk'))
if self.kwargs.get('subcategory_pk'):
context['subcategory_shown']=get_object_or_404(SubCategory, pk=self.kwargs.get('subcategory_pk'))
# print(context)
return context
After FiddleStix' answer (I haven't gone thu bluegrounds thread yet), I tried and managed to make everything work except ItemDetailView.
The urls are working fine and the filtering of get_queryset function is working fine, however,
Question 1: I wonder this might not be DRY enough?! Nevertheless, it's working. so thanks!! But could it be dryer??
Question 2: when ItemDetailView runs, the urls appear to be correct, however, the page redirect to a page rendering all items from all categories...
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
def get_object(self):
print(get_object_or_404(Item, pk=self.kwargs.get('item_pk')))
return get_object_or_404(Item, pk=self.kwargs.get('item_pk'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
# context['categories'] = Category.objects.all()
print(context)
return context
Neither is the object nor the context being printed out... and it doesn't prompt any error, either. I must have made a silly mistake somewhere!!
Answer to Question 2:
view.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
The DetailView class should be simple if no customisation is needed, the problem is in the urlpatterns:
url.py
urlpatterns = [
path('item_<int:pk>', view.ItemDetailView.as_view(), name='item'),
]
Always use <pk> as the value passed in DetailView as it's the default. I used item_<int:item_pk> as the path url. That's why I had to use get_object() to manually get the item object (to override the default get_object()). As #bluegrounds answer suggests that the reason Class-based Views work well is they save time, for the default functions they possess.
If you wish to use item_<int:item_pk>, CBV offers the flexibility as well: simply override pk_url_kwargs = 'item_pk' in the View class - Feel free to check my other question: DetailView - get_object function confusion for clarification. #neverwalkaloner 's answer is very straightforward.
So first things first, CBVs...
They work by having "default" behaviours built into each one of them. For the ListView class, take this example view:
class ArticleListView(ListView):
model = Article
along with an example urlpattern:
path('all-articles/', ArticleListView.as_view())
and that's it. This is all that is essential for a ListView to work. This ArticleListView view would look for a template called article_list.html and in it you can use the context variable object_list to access all the Article objects that the class gets for you without you having to write the QuerySet explicitly.
Of course you can change these values, and customize the QuerySet, and do all kinds of things, but for that you'll have to study the docs. I personally find ccbv much easier to read than the docs. So for example you can see in ccbv's page about ListViews that the context_object_name = None which defaults to object_list, as mentioned above. You can change that to, for example context_object_name = 'my_articles'. You could also set the template_name = 'my_articles.html' and that will override the default template name pattern of <model>_list.html.
Now, about your code,
If you're sure that you want your URL structure to stay like it is, you could have your class view as follows to get the functionality you need:
class ItemListView(ListView):
template_name = 'boutique/items.html'
context_object_name = 'categories'
paginate_by = 12
def get_queryset(self):
# This method should return a queryset that represents the items to be listed in the view.
# I think you intend on listing categories in your view, in which case consider changing the view's name to CategoryListView. Just sayin'...
# An instance of this view has a dictionary called `kwargs` that has the url parameters, so you can do the following:
# You need some null assertions here because of the way you've setup your URLs
qs = Categories.objects.filter(men=self.kwargs['gender'], pk=self.kwargs['category_pk'])
return qs
As you can see, we didn't set many things in this class view for it to work. Namely, we didn't set the model variable as we did previously. That's because we wouldn't need it. The part that uses that variable was in the default get_queryset() method and we've overridden that method. See CCBV for more info on the default implementation of get_queryset().
Now the template will be supplied with the objects from get_queryset(), under the name categories, because that's what we set context_object_name's value to be.
NOTE: The variable model is used in other places other than get_queryset() such as the default template_name. The default template name is derived from the model name and the template_name_suffix. So if you don't set the model variable, make sure to set the template_name manually.
I'm not sure about your application's logic but I think you should change the Category model to have only one Boolean field that denotes gender. For example men if it is True and women if it's False. That way a category can't be for both men and women at the same time (unless that is something you need), and it also can't be for neither, because currently you can have a category be false for the both gender fields, which doesn't really make sense.
I would actually suggest a completely different solution that involves CHOICES, as such:
gender = models.IntegerField(null=False, CHOICES=[(1,'Men'), (2,'Women'), (3,'Other'), (4,'Unisex')], default=3)
This would store a number in the database that denotes the gender, and in your app you'll only see the correlating string (gender) to that number.
I have not tried this code on my machine so I might have missed a few things, but I hope I clarified the overall workings of CBVs.
To answer you main question, your ItemList should set model=models.Item, as alluded to in the error message, because it is meant to be a list of items.
I would set up your urls.py so that /items/ or /item_list/ goes to ItemListView.as_view(). If you then want to filter your list of items, I would not do it by changing the URL to /items/women/. Instead, I would use a URL query string like /items/?gender=women. How to do that is explained here but basically:
class ItemListView(ListView):
model = Item
template_name = "item_list.html"
paginate_by = 100
def get_queryset(self):
filter_val = self.request.GET.get('gender', 'some_default')
queryset = Item.objects.filter(
... # You'll have to work this bit out
)
return queryset
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['gender'] = self.request.GET.get('gender', 'some_default')
return context

Django template sort by 'unrelated' model's field

I'm trying to sort related items in a template by a field in a model three ForeignKey relationships away. I'm assembling the data for the template in the view as proposed in another StackOverflow answer:
Sorting related items in a Django template
As far as I can tell, I copied the code from this as-is except for I had to change variable names. It doesn't throw an error, it just displays no list items in the HTML unordered list.
# checkout.html
{% for item in cart_items %}
<tr>
<td class="left">
{{ item.name }}
<ul>
{% for part in part_list %}
<li>{{ part.name }}
{% endfor %}
</ul></td>
</tr>
{% endfor %}
And the view...
# views.py
def checkout(request):
cart_items = get_cart_items(request)
itemCollection = []
for item in cart_items:
item.part_list = item.product.buildpart.all().order_by('product__family__type')
itemCollection.append(item)
return render(request, 'checkout.html', locals())
And the get_cart_items function:
# cart.py
def get_cart_items(request):
""" return all items from the current user's cart """
return CartItem.objects.filter(cart_id=get_cart_id(request))
As I said, the template and view are pretty much copies of the solution presented in the aforementioned StackOverflow article. One thing I thought was curious was that itemCollection[] from the view is never referenced in the template.
I believe the order_by clause ('product__family__type') is right only because it doesn't generate an error. But in case that is the problem or a part of it here is the chain of models I am attempting to navigate in that order_by clause:
We start from the shopping cart model (CartItem):
class Item(models.Model):
cart_id = models.CharField(max_length=50)
quantity = models.IntegerField(default=1)
product = models.ForeignKey(PartModel, unique=False)
class Meta:
abstract = True
class CartItem(Item):
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['date_added']
verbose_name = "Cart Item"
Through the 'product' field we get to the model holding our inventory and its self-referential BuildPart ManyToMany model:
class PartModel(models.Model):
family = models.ForeignKey(PartFamily)
name = models.CharField("Model Name", max_length=50, unique=True)
buildpart = models.ManyToManyField('self', through='BuildPart',
symmetrical=False, related_name='+')
class Build(models.Model):
build = models.ForeignKey(PartModel, related_name='+')
part = models.ForeignKey(PartModel, related_name='+')
quantity = models.PositiveSmallIntegerField(default=1)
class Meta:
abstract = True
unique_together = ('build', 'part')
def __unicode__(self):
return self.build.name + ' with ' + str(self.quantity) + ' * ' + \
self.part.family.make.name + ' ' + self.part.name
class BuildPart(Build):
pass
class Meta:
verbose_name = "Build Part"
From there we follow the 'family' field to the PartFamily model:
class PartFamily(models.Model):
make = models.ForeignKey(PartMake)
type = models.ForeignKey(PartType)
name = models.CharField("Family Name", max_length=30,
unique=True)
slug = models.SlugField(unique=True)
And lastly, we get to the model with the 'order' field, the one we wish to sort the related items by, PartType:
class PartType(models.Model):
name = models.CharField("Part Type", max_length=30, unique=True)
slug = models.SlugField(unique=True)
order = models.PositiveSmallIntegerField()
description = models.TextField(blank=True, null=True)
To recap, how do I get the shopping cart products' related items, and sort them by the 'order' field in the PartType model?
You have two errors, both in the template.
Firstly, you've put your items with the sorted relationship in a list called itemCollection, but in the template you're iterating over cart_item instead. This is a very good example of why you should be explicit about what variables you pass to the template, rather than relying on locals().
Secondly, you then iterate over part_list without defining it. You mean item.part_list.

Django ModelForm with foreign key

I'm trying to create a ModelForm that updates a table with foreign keys. What I have seems to work, but I was hoping someone could tell me if there's a better way to do this or if there's a problem with the way I'm doing it below.
Is it correct to use the queryset on the Author and Genres table? It feels like I should be using a queryset on the Book model, and relate the foreign key to those tables.
models.py:
class Author(models.Model):
name = models.CharField(max_length=200)
class Genre(models.Model):
name = models.CharField(max_length=200)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey('Author')
genre = models.ForeignKey('Genre')
forms.py:
class BookForm(ModelForm):
title = forms.CharField(max_length=200)
author = forms.ModelChoiceField(queryset=Author.objects.all())
genre = forms.ModelChoiceField(queryset=Genre.objects.all())
class Meta:
model = Book
fields = ['title', 'author', 'genre']
views.py:
def add_book(request):
if request.method == 'POST':
form = BookForm(request.POST)
if form.is_valid():
form.save(commit=True)
return HttpResponseRedirect('/add/')
else:
form = BookForm()
The only thing that's wrong with this code is you are overriding the default behaviour of your model form.
Change it to look like:
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['title', 'author', 'genre']
And let django handle with the definition of those.
if you need to add labels or widgets, you can define them in the Meta class:
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['title', 'author', 'genre']
labels = {'title': 'Book title', }
For example.
I'm not really sure what you're asking here, or why you would want a queryset on Book.
You haven't actually changed any of the defaults on any of the fields, so there is no need to redefine them: your form could be simply
class BookForm(ModelForm):
class Meta:
model = Book
and it would work exactly the same.

Categories

Resources