Django: Parent categories doesn't shown in admin interface - python

Problem:
Dash signs display in 'Parent category' column before I open any menu item. I get 'Parent category' items when open menu item profile(see admin.py below)
Before and after opening of menu profile: picture1 and picture2 (pay attention on Parent Category column)
My models.py: (attention on parent_id)
class Menu(models.Model):
cat_title = models.CharField(max_length=150, verbose_name='Category title')
menu_title = models.CharField(max_length=150, verbose_name='Menu title')
parent_id = models.IntegerField(blank=True, null=True, verbose_name='Parent category', choices=(('',''),))
url = models.CharField(max_length=255, verbose_name='URL', blank=True)
named_url = models.CharField(max_length=255, verbose_name='Named URL', blank=True)
level = models.IntegerField(default=0, editable=False)
My admin.py: ()
class MyMenu(admin.ModelAdmin):
def get_choices(self):
choices = (('',''),)
categories = models.Menu.objects.all().values()
for i in categories:
choices += ((i['id'], i['cat_title']),)
return choices
def formfield_for_choice_field(self, db_field, request):
if db_field.name == 'parent_id':
db_field.choices = self.get_choices()
return super().formfield_for_choice_field(db_field, request)
list_display = ('cat_title', 'menu_title', 'parent_id', 'level')
list_display_links = ('cat_title', 'menu_title')
admin.site.register(models.Menu, MyMenu)
Question: How could I rewrite my admin.py to show parent_id items without opening any menu item profile?
I've already tried Model.get_FOO_display() but it doesn't work in right way. Any help would be appreciated.

Without changing your model, the simplest solution is probably to add a parent method in your admin and use it instead of "parent_id" in the list_display list:
class MyMenu(admin.ModelAdmin):
# ....
def parent(self, obj):
if obj.parent_id:
return Menu.objects.get(pk=obj.parent_id).cat_title
return ""
list_display = ('cat_title', 'menu_title', 'parent', 'level')
# Unrelated but you may also want to rewrite `get_choices`
# in a simpler and more performant way:
def get_choices(self):
choices = models.Menu.objects.values_list("id", "cat_title"))
return (('',''),) + tuple(choices)
Or to make parent a method or property on your Menu model:
class Menu(models.Model):
# ...
# you may want to use django's `cached_property` instead
# but then you'll have to invalidate the cache when setting
# (or unsetting) `.parent_id`
#property
def parent(self):
if not self.parent_id:
return None
return Menu.objects.get(pk=self.parent_id)
and add "parent" to your admin's list_display.
BUT since Menu.parent_id is actually a foreign key on Menu, the proper solution is to declare it as such in your model:
class Menu(models.Model):
cat_title = models.CharField(max_length=150, verbose_name='Category title')
menu_title = models.CharField(max_length=150, verbose_name='Menu title')
parent = models.ForeignKey("self", blank=True, null=True, related_name="children")
# etc

Related

Filtering Foreign key choices in Django serializer

I have a Django model that looks like the code below. At the moment, when I am using django rest framework to create a new menu instance, the dish column contains options created by all users on the platform.
How should I go about filtering the dish column so it only has options created by the user?
Should I be doing it in the views or serializer?
Thank you for the response in advance.
class Dish(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, null=True, blank=True)
title = models.CharField(max_length=280)
description = models.CharField(max_length=280)
image = models.ImageField(upload_to='static/images/post_image',
default='static/images/post_image/default.jpg')
def __str__(self):
return f'{self.title}'
def get_image_url(self, obj):
return obj.image.url
class Menu(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, null=True, blank=True)
title = models.CharField(max_length=280)
description = models.CharField(max_length=280)
dish = models.ManyToManyField(Dish)
price = models.SmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(10000)], default=None)
def __str__(self):
return f'{self.title}'
This is how I ended up doing it for those who have the same problems in the future.
class UserDishForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
user = self.context['request'].user
return Dish.objects.filter(user=user)
class MenuCreateSerializer(serializers.ModelSerializer):
dish = UserDishForeignKey(many=True)
class Meta:
model = Menu
fields = ['title', 'description', 'dish', 'price', ]
read_only_fields = ['user', ]
def get_user(self, obj):
return str(obj.user.username)
Assuming that you have an user object, you can get all dishes associated to that user like this:
user.dish_set
If you want to find all menu's that are having particular menus by dish's owner. That can be done like
Menu.objects.filter(dish__user=user)
Placement of this depends on what you are trying to achieve. If you want to validate the input, placement should be in serializer
class UserDishForeignKey(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
user = self.context['request'].user
return Dish.objects.filter(user=user)
class MenuCreateSerializer(serializers.ModelSerializer):
dish = UserDishForeignKey(many=True)
class Meta:
model = Menu
fields = ['title', 'description', 'dish', 'price', ]
read_only_fields = ['user', ]
def get_user(self, obj):
return str(obj.user.username)

Customize foreign key dropdown in Django Admin Site

I'm having trouble finding the best way to override and add custom html to an edit/add model form in my Django admin site.
Here are the two models involved here:
Icon model used to store "Font Awesome" Icons:
class Icon(models.Model):
name = models.CharField(max_length=100, null=False)
style = models.CharField(max_length=10, choices=STYLE_CHOICES, null=False)
retired = models.BooleanField(default=False)
def delete(self):
self.retired = True
self.save()
objects = NotRetiredManager()
objects_deleted = DeletedManager()
def __str__(self):
return self.name
Workbook model that holds foreign key reference to the above Icon model:
class Workbook(models.Model):
client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True)
icon = models.ForeignKey(Icon, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=100)
workbookLink = models.CharField(max_length=1000)
retired = models.BooleanField(default=False)
def delete(self):
self.retired = True
self.save()
objects = NotRetiredManager()
objects_deleted = DeletedManager()
def __str__(self):
return self.name
Here are the overridden admin models for the above models:
class BaseAdmin(AdminImageMixin, admin.ModelAdmin):
def delete_queryset(self, request, queryset):
for obj in queryset:
obj.delete()
#admin.register(Workbook)
class WorkbookAdmin(BaseAdmin):
list_display = ("name", "client")
list_filter = (NameFilter, ClientNameFilter)
ordering = ("name", )
#admin.register(Icon)
class IconAdmin(BaseAdmin):
fields = ("name", "style", "icon_display")
list_display = ("icon_display", "name", "style" )
list_display_links = ("icon_display", "name")
list_filter = (NameFilter, )
ordering = ("name", )
def icon_display(self, obj):
return mark_safe(f'<i class="{obj.style}{obj.name}"></i>')
readonly_fields = ["icon_display"]
Here is a list display of some Icons I have in my database:
Currently, the add/edit page for a Workbook on my Admin Site looks like this:
I would like for that dropdown in that second screenshot to be customized similar to the "Icon Display" column in that first screenshot so that a user would choose from graphical list of icons as opposed to the default choicefield form containing the Icon names.
I've looked into the Django docs as well as similar questions on here such as this Similar Stack Overflow Question; however, I'm not fully understanding the proper way to implement something like this.
I hope the information I provided about my app is useful. Please let me know if you'd like me to provide any additional information, or add any clarifications!

get() returned more than one Children -- it returned 2! in django

I have a linked model:
class Children(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
child_name = models.CharField(max_length=150, null=True, blank=True)
slug = AutoSlugField(populate_from='child_name')
blood_group = models.CharField(max_length=5, blank=True)
class Meta:
unique_together = ('slug', 'person')
def get_absolute_url(self):
return self.person.get_absolute_url()
def get_delete_url(self):
return reverse(
'member:children-delete',
kwargs={
'person_slug': self.person.slug,
'children_slug': self.slug})
def get_update_url(self):
return reverse(
'member:children-update',
kwargs={
'person_slug': self.person.slug,
'children_slug': self.slug})
my forms.py:
class ChildrenForm( SlugCleanMixin, forms.ModelForm):
class Meta:
model = Children
exclude = ('person',)
def clean(self):
cleaned_data = super().clean()
slug = cleaned_data.get('slug')
person_obj = self.data.get('person')
exists = (
Children.objects.filter(
slug__iexact=slug,
person=person_obj,
).exists())
if exists:
raise ValidationError(
"Children with this Slug "
"and Person already exists.")
else:
return cleaned_data
def save(self, **kwargs):
instance = super().save(commit=False)
instance.person = (
self.data.get('person'))
instance.save()
self.save_m2m()
return instance
views.py:
class ChildrenCreate( ChildrenFormMixin, ChildrenGetObjectMixin,
PersonContextMixin,CreateView):
template_name = 'member/children_form.html'
model = Children
form_class = ChildrenForm
class ChildrenUpdate(ChildrenFormMixin, ChildrenGetObjectMixin,
PersonContextMixin,UpdateView):
template_name = 'member/children_form.html'
model = Children
form_class = ChildrenForm
slug_url_kwarg = 'children_slug'
class ChildrenDelete(ChildrenFormMixin,ChildrenGetObjectMixin,
PersonContextMixin,DeleteView):
model = Children
slug_url_kwarg = 'children_slug'
def get_success_url(self):
return (self.object.person
.get_absolute_url())
my utils.py:
class ChildrenFormMixin():
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
if self.request.method in ('POST', 'PUT'):
self.person = get_object_or_404(
Person,
slug__iexact=self.kwargs.get(
self.person_slug_url_kwarg))
data = kwargs['data'].copy()
data.update({'person': self.person})
kwargs['data'] = data
return kwargs
class ChildrenGetObjectMixin():
def get_object(self, queryset=None):
person_slug = self.kwargs.get(
self.person_slug_url_kwarg)
children_slug = self.kwargs.get(
self.slug_url_kwarg)
return get_object_or_404(
Children,
slug__iexact=children_slug,
person__slug__iexact=person_slug)
class PersonContextMixin():
person_slug_url_kwarg = 'person_slug'
person_context_object_name = 'person'
def get_context_data(self, **kwargs):
person_slug = self.kwargs.get(
self.person_slug_url_kwarg)
person = get_object_or_404(
Person, slug__iexact=person_slug)
context = {
self.person_context_object_name:
person,
}
context.update(kwargs)
return super().get_context_data(**context)
The children created more than one for same name of same parents. When I tried to edit children it gives "get() returned more than one Children -- it returned 2!" error. In traceback, it said, 'person__slug__iexact=person_slug' is the direct causes of this traceback.
In the form, I added clean method to catch the error and maintain uniqueness of children name of same parents but it not worked. Could I get suggestions where I do wrong?
Edit:
my Person model:
class Person(models.Model):
name = models.CharField(max_length=250)
slug = AutoSlugField(populate_from='name')
birth_date = models.DateField(null=True, blank=True)
blood_group = models.CharField(max_length=5)
present_address = models.CharField(max_length=250, blank=True)
permanent_address = models.CharField(max_length=250, blank=True)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name='member_persons')
class Meta:
ordering = ['name']
unique_together = ['name', 'birth_date']
I believe you are using AutoSlugField from django-autoslug, and you trying to get by non-unique field. AutoSlugField won't make your field unique by default, from docs:
AutoSlugField can also perform the following tasks on save:
populate itself from another field (using populate_from),
use custom slugify function (using slugify or Settings), and
preserve uniqueness of the value (using unique or unique_with).
None of the tasks is mandatory, i.e. you can have auto-populated non-unique fields, manually entered unique ones (absolutely unique or within a given date) or both.
So quick fix would be slug = AutoSlugField(populate_from='child_name', unique=True)
UPDATE(Since you posted your Person model)
The problem is the same and solution is the same.
Explanation:
For example you have two Person objects:
id name slug birth_date
1 alex alex 10.10.2016
2 alex alex 10.10.2015
This won't violate unique_together = ['name', 'birth_date']
And you got two Children objects:
id name slug person_id
1 john john 1
2 john john 2
And that won't violate unique_together = ('slug', 'person') neither
Then you are making query
get_object_or_404(
Children,
slug__iexact='john',
person__slug__iexact='alex')
Which would match two objects. So you got problem. Quick fix would be to make slug unique=True.

ModelChoiceField initial value from db

I tried to find out how to set the initial value of a ModelChoiceField, I found many answers to this question but I don't really get them. I understand that I can set "initial" when calling the form in admin.py but then a model instance is mentioned and I am lost.
This is my models.py
class Articles(models.Model):
headline = models.CharField('Rubrik', max_length=200)
category = models.CharField('Kategori', max_length=200, blank=True)
extract = models.TextField('Utdrag')
image = ImageField('Bild', upload_to='articles', blank=True, default="")
text = RichTextUploadingField('Text', blank=True, default="")
added = models.DateTimeField('Publicerad', default=timezone.now, blank=True)
updated = models.DateTimeField('Uppdaterad',auto_now=True)
frontpage = models.BooleanField('Visa på startsida', default=True)
active = models.BooleanField('Aktiv', default=False)
def save(self, *args, **kwargs):
if self.added is None:
self.added = timezone.now
super(Articles, self).save(*args, **kwargs)
def __unicode__(self):
return '%s' % (self.headline)
class Meta:
verbose_name = "artikel"
verbose_name_plural = "Artiklar"
This is my forms.py
class ArticleForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=Menu.objects.order_by('name').filter(category=True))
This is my admin.py
class ArticlesAdmin(admin.ModelAdmin):
form = ArticleForm
list_display = ('headline','category', 'extract', 'image', 'added', 'updated', 'frontpage', 'active')
admin.site.register(Articles, ArticlesAdmin)
When I edit the article in the admin section I want the stored value of the category to be the initial value for the ModelChoiceField. Do you get what I mean?
In admin.py there should be something like:
form = ArticleForm(initial = {'category': instance.something})
*EDIT: I added ForeignKey as suggested
category = models.ForeignKey(Menu)
and admin.py looks like this:
class ArticlesAdmin(admin.ModelAdmin):
form = ArticleForm
list_display = ('headline','category', 'extract', 'image', 'added', 'updated', 'frontpage', 'active')
And now it's working as expected!
This code should work:
form = ArticleForm(initial = {'category': pk})
pk is the stored value, pk = primary key

django-mptt and ForeignKey with blank=True

I am using django-mptt in my project
models.py:
class Category(models.Model):
name = models.TextField()
parent = models.ForeignKey("self", blank=True, null=True,
related_name="sub_category")
image = models.ImageField(upload_to="categories", blank=True)
mptt.register(Category)
admin.py:
class CategoryAdmin(MPTTModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'parent':
field = TreeNodeChoiceField(
queryset=Category.objects.all(),
level_indicator = u'+--',
)
else:
field = super(CategoryAdmin, self).formfield_for_dbfield(
db_field, **kwargs)
return field
admin.site.register(Category, CategoryAdmin)
The problem is -- I can't choose no parent, TreeNodeChoiceField doesn't show '---' in select.
What could you recommend?
Use required=False when initializing the TreeNodeChoiceField object.
field = TreeNodeChoiceField(
required=False,
queryset=Category.objects.all(),
level_indicator = u'+--',
)
Django form fields default to required=True, in this case that means disallowing None as a value.

Categories

Resources