I have two models:
class ProductCategory(models.Model):
'''
Product Category determines to which category the product falls into.
'''
category_name = models.CharField(max_length=254)
def __unicode__(self):
return u"%s" %(self.category_name)
class Meta:
verbose_name_plural = "Product Categories"
class Product(models.Model):
'''
Product Information
'''
name = models.CharField(max_length=250)
category = models.ForeignKey(ProductCategory)
def __unicode__(self):
return u"%s" %(self.product_name)
I want to apply autocomplete on category field of product model. Thus,
class ProductCategoryAutoComplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['category_name']
model = Product
choices = ProductCategory.objects.all()
autocomplete_light.register(Product, ProductCategoryAutoComplete)
I have included the template too. Everything works fine. Except when I choose the category and submit the form html field is required is popping up in the bottom. What is wrong?
Edit: Form
class ProductCreateForm(autocomplete_light.ModelForm):
category = forms.ModelChoiceField(queryset=ProductCategory.objects, widget=autocomplete_light.ChoiceWidget('ProductCategoryAutoComplete'))
class Meta:
model = Product
Oops !
Product.category is an FK to model Category, but the Autocomplete you are passing (ProductCategoryAutoComplete) is registered for Product model ! Field that should allow selecting a Category should use an autocomplete for Category, not one for Product ;)
Better usage of autocomplete_light.ModelForm
Since you're using autocomplete_light.ModelForm, you don't have to specify the field. Just register an autocomplete for Category:
autocomplete_light.register(Category, search_fields=['category_name'])
And let autocomplete_light.ModelForm do the field definition:
class ProductCreateForm(autocomplete_light.ModelForm):
class Meta:
model = Product
Yes, that's all you need ;)
search_fields is wrong, product does`n have field category_name, if I understand your idea it have to be:
class ProductCategoryAutoComplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['category__category_name']
model = Product
choices = ProductCategory.objects.all()
autocomplete_light.register(Product, ProductCategoryAutoComplete)
Related
I just learned django last January and I'm currently working on website to store and show lyrics. My problem is that in createview the fields are not sorted.
Here is my models.py:
class Singer(models.Model):
song_singer = models.CharField(max_length=200)
def __str__(self):
return self.song_singer
def get_absolute_url(self):
return reverse('singer_author')
class Lyrics(models.Model):
title = models.CharField(max_length=50)
singer = models.ForeignKey(Singer, on_delete=models.RESTRICT)
type = models.ForeignKey(Type, on_delete=models.RESTRICT)
lyrics = models.TextField()
Views.py:
class AddSongView(LoginRequiredMixin, CreateView):
model = Lyrics
fields = ['title', 'singer', 'type', 'lyrics']
Screenshot in the browser
As you can see in the attached screenshot, the choices from singer field is not sorted. How to sort those choices? Thank you!
The easiest way to do this is by defining a default ordering=… [Django-doc] on your Singer model, this will then order the items by name if you do not specify another ordering:
class Singer(models.Model):
# …
class Meta:
ordering = ['song_singer']
Best to use a ModelForm here. This also gives you the chance to modify your form even more:
from django import forms
class SongForm(forms.ModelForm):
singer = forms.ModelChoiceField(queryset=Singer.objects.all().order_by('song_singer'))
class Meta:
model = Lyrics
fields = ['title', 'singer', 'type', 'lyrics']
class AddSongView(LoginRequiredMixin, CreateView):
form_class = SongForm
I have this model called Menu which has a many-to-many relationship with another model called category. Both this models have a field called is_active which indicates that menu or category is available or not.
Alright, then I have an api called RestaurantMenus, which returns all active menus for a restaurant with their categories extended, the response is something like this:
Menu 1
Category 1
Category 2
Menu 2
Category 3
Menu 3
Category 4
Category 5
Category 6
Now what I try to achieve is to only seriliaze those menus and categories which are active (is_active = True). To filter active menus is simple but to filter its children is what I'm struggling with.
My Models:
class Category(models.Model):
menu = models.ForeignKey(Menu, related_name='categories', on_delete=models.CASCADE)
name = models.CharField(max_length=200)
class Menu(models.Model):
name = models.CharField(max_length=200)
My Serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class MenuSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True, read_only=True)
class Meta:
model = Menu
fields = "__all__"
P.S. Category model itself has a many-to-many relationship with another model Called Item, which has an is_active field too. I want the same effect for those too but I cut it from the question cause I think the process should be the same. So actually the api response is something like this:
Menu 1
Category 1
Item 1
Item 2
Item 3
Category 2
Item 4
Using a SerializerMethodField is pretty straightforward.
You can filter inside get_< attribute >, you serialize the queryset and return the data from this serialized queryset.
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class MenuSerializer(serializers.ModelSerializer):
categories = serializers.SerializerMethodField('get_categories')
def get_categories(self, menu):
qs = Category.objects.filter(menu=menu, is_active=True)
serializer = CategorySerializer(qs, many=True)
return serializer.data
class Meta:
model = Menu
fields = "__all__"
If you have a third nested class, you can just apply the same simple logic to CategorySerializer using a get_items and ItemSerializer.
This approach lets you filter Menu objects to check is_active = True on your views.py since this is business logic.
Here you can use Django Prefetch objects. This lets you define queryset to choose the set objects from.
So, you would write your Menu retrieval query something like this:
from django.db.models import Prefetch
menus = Menu.objects.filter(is_active=True).prefetch_related(Prefetch('categories', queryset=Category.objects.filter(is_active=True)))
To add category items as well to the result set, use the following query:
menus = Menu.objects.filter(is_active=True).prefetch_related(Prefetch('categories', queryset=Category.objects.filter(is_active=True)), Prefetch('categories__items', queryset=Item.objects.filter(is_active=True))))
This should solve your problem.
Note: I have not tested the code so you might need to make some modifications.
I assume you define your models like this
Menu
is_active
categories: ManyToManyField (related_name='menus')
Category
is_active
items: ManyToManyField (related_name='categories')
Item
is_active
E.g.
def get_categories():
active_category_qs = Category.objects.filter(is_active=True)
active_menu_qs = Menu.objects.filter(is_active=True,
categories__in=active_category_qs)
categories = []
for menu in active_menu_qs.iterator():
for category in menu.categories.all():
if category.id not in [
obj_target["id"] for obj_target in categories
]:
category_data = CategorySerializer(category).data # just get the data of category object
categories.append(category_data)
return categories
try this:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class MenuSerializer(serializers.ModelSerializer):
category = SerializerMethodField()
def get_category(self,instance):
qs = Category.objects.filter(menu__id=instance.id)
data = CategorySerializer(qs,many=True).data
return data
class Meta:
model = Menu
fields = "__all__"
You can try like this using list_serializer_class:
class FilterActiveListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(is_active=True)
return super(FilterActiveListSerializer, self).to_representation(data)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
list_serializer_class = FilterActiveListSerializer
class CategorySerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Category
list_serializer_class = FilterActiveListSerializer
class MenuSerializer(serializers.ModelSerializer):
categories = CategorySerializer(many=True, read_only=True)
class Meta:
model = Menu
item_instance = Item.objects.filter(is_active = True, category__is_active=True)
you will get all active items that have an active category using this.
fetch all items and create JSON data from them.
upvote if it helps
I want to use serializer method field to fetch data with some business rule
class Product(models.Model):
name = models.CharField()
class Stock(models.Model):
product = models.ForeignKey(Product, on_delete=models.Cascade, related_name='stock')
current = models.BooleanField(default=True)
quantity = models.IntegerField()
class ProductSerializer(serializers.ModelSerializer):
productstock = serializers.SerializerMethodField()
#GET LATEST CURRENT STOCK-QUANTITU
class Meta:
model = Product
fields = [
'name',
'productstock'
]
I want to get an output like this:
{
name:'laptop',
productstock:18
}
I was not able check it right now but it should be something like this:
class ProductSerializer(serializers.ModelSerializer):
productstock = serializers.SerializerMethodField()
#GET LATEST CURRENT STOCK-QUANTITU
class Meta:
model = Product
fields = [
'name',
'productstock'
]
def get_productstock(self, obj):
stock = Stock.objects.filter(product__name=obj.name)
return stock.quantity
Explanation:
the serializer method is always built up by get_ + variable_name and takes the curren object as an argument. Then you should be able to filter your Stock model and the productstock value will become what you return by this method. Hope it helps you.
Update
I guess now I got your point. You should do the filtering before you pass your queryset through the serializer. Therefore I would recommend to override the get_queryset method. In your view just add the below function and it just filters only the products with current=True. My example is with APIView but it works with every DRF View as they all inherit the APIView.
class YourViewClass(APIView):
...
def get_queryset(self):
return Product.objects.filter(stock__current=True)
Explanation
Within the filter method you refer to your related_name of the foreign key field and then use double underscore plus the field you want to filter on. Hope it works for you.
I've two models as below.
class Category(models.Model):
title = models.CharField(max_length=55)
class Meta:
verbose_name = 'Food Category'
verbose_name_plural = 'Food Categories'
def __str__(self):
return self.title
class FoodItem(TimeStampWithCreator):
CATEGORY_CHOICES = (
('takeway', 'Takeaway'),
('dine_in', 'Dine In'),
('function', 'Function'),
)
type_menu_select = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='takeway')
category = models.ForeignKey(FoodCategory, on_delete=models.CASCADE)
i want to filter all the categories containing takeaway, I've no idea how to achieve this
You've included category choices in your FoodItem model but the model also has a ForeignKey to a Category model, this isn't needed if the only category objects you have are those three choices (the category field must refer to one of those anyway as it's the ForeignKey). To filter the items by category you would need to use a queryset filter.
https://docs.djangoproject.com/en/3.0/topics/db/queries/#retrieving-specific-objects-with-filters
FoodItem.objects.filter(category=YourCategory)
This is usually the kind of thing I'd like to test in the shell as I don't do it very often. If what you want is actually all Category with a FoodItem that have type_menu_select set to 'takeway' then the following should work (but I haven't tested it):
Category.objects.filter(fooditem__type_menu_select='takeway')
This is using the "reverse" relationship on the ForeignKey and there's more information in the Django docs (search for 'reverse').
what I want to achieve is user will submit 3 inputs in the form 1) name 2) dropdown to select technician, 3) multiselect dropdown to select multiple products. Once the user submit the details
it will generate one lead in database with value like name,foreignkey of selected technician and id of selected products in different table. I don't know how to achieve this below I have mentioned my approch to achieve what I want. Please let me know if the models need any changes and how I can write a view for the same.
models.py
class product(models.Model):
name = models.CharField(max_length=20)
class technician(models.Model):
name = models.CharField(max_length=20)
class lead(models.Model):
name = models.CharField(max_length=20)
technician = models.ForeignKey(technician,on_delete=models.SET_NULL,null=True) #only single selection
products = models.ManyToManyField(product) #user can select multiple product in dropdown
form.py
class leadForm(form.ModelForm):
products = forms.MultipleChoiceField(queryset=Product.objects.all())
technician = forms.CharField(max_length=30,choices=[(i.id,i.name) for i in Technician.objects.all().values('id','name')
class Meta:
model = lead
fields = ('name','technician')
You should use a ModelMultipleChoiceField [Django-doc] here. The But in fact you do not need to implement the models yourself. You can simply let the Django logic do the work for you.
In order to give a textual representation at the HTML end, you can override the __str__ functions of the models:
class Product(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Technician(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Lead(models.Model):
name = models.CharField(max_length=20)
technician = models.ForeignKey(Technician, on_delete=models.SET_NULL, null=True)
products = models.ManyToManyField(Product)
Then we can simply define our form with:
class LeadForm(form.ModelForm):
class Meta:
model = Lead
fields = '__all__'
Note: usually classes are written in PamelCase and thus start with an Uppercase.
You can here use a class-based CreateView [Django-doc] for example:
from django.views.generic.edit import CreateView
from app.models import Lead
from app.forms import LeafForm
class LeadCreateView(CreateView):
model = Lead
form_class = LeadForm
template_name = 'create_lead.html'