Properly using Foreign Key references in search_fields, Django admin - django

I've got a weird conundrum that I need some help with in Django 1.8.4 using python 3.4 in a virtual-env.
I've got 2 models in 2 different apps... as follows with multiple Foreign Key references.
Inventory App
class InventoryItem(models.Model):
item_unique_code = models.CharField(max_length=256, blank=False, null=False)
category = models.CharField(max_length=256, blank=False, null=False,choices=[('RAW','Raw Material'),('FG','Finished Good'),('PKG','Packaging')])
name = models.CharField(max_length=64, blank=False, null=False)
supplier = models.CharField(max_length=96, blank=False,null=False)
approved_by = models.CharField(max_length=64, editable=False)
date_approved = models.DateTimeField(auto_now_add=True, editable=False)
comments = models.TextField(blank=True, null=True)
def __str__(self):
return "%s | %s | %s" % (self.item_unique_code,self.name,self.supplier)
class Meta:
managed = True
unique_together = (('item_unique_code', 'category', 'name', 'supplier'),)
Recipe App
class RecipeControl(models.Model):
#recipe_name choice field needs to be a query set of all records containing "FG-Finished Goods"
recipe_name = models.ForeignKey(items.InventoryItem, related_name='recipe_name', limit_choices_to={'category': 'FG'})
customer = models.ForeignKey(customers.CustomerProfile, related_name='customer')
ingredient = models.ForeignKey(items.InventoryItem, related_name='ingredient')
min_weight = models.DecimalField(max_digits=16, decimal_places=2, blank=True, null=True)
max_weight = models.DecimalField(max_digits=16, decimal_places=2, blank=True, null=True)
active_recipe = models.BooleanField(default=False)
active_by = models.CharField(max_length=64, editable=False)
revision = models.IntegerField(default=0)
last_updated = models.DateTimeField(auto_now_add=True, editable=False)
def __str__(self):
return "%s" % (self.recipe_name)
class Meta:
managed = True
unique_together = (('recipe_name', 'customer', 'ingredient'),)
I've been getting some weird results in my Recipe's Admin class...
from django.contrib import admin
from django.contrib.auth.models import User
from .models import RecipeControl
from Inventory import models
class RecipeView(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.active_by = request.user.username
obj.save()
fieldsets = [
('Recipe Information', {'fields': ['recipe_name', 'customer']}),
('Ingredients', {'fields': ['ingredient','min_weight','max_weight','active_recipe']}),
('Audit Trail', {'fields': ['active_by','revision','last_updated'],'classes':['collaspe']}),
]
list_select_related = ['recipe_name','customer','ingredient']
search_fields = ['recipe_name','customer','ingredient','active_by']
readonly_fields = ('last_updated','active_by')
list_display = ['recipe_name','customer','ingredient','min_weight','max_weight','last_updated','active_by', 'active_recipe']
admin.site.register(RecipeControl, RecipeView)
The issue I've come across is if I try to do a search on any ForeignKey field, Django throws this error...
Exception Type: TypeError at /admin/Recipe/recipecontrol/
Exception Value: Related Field got invalid lookup: icontains
According to the Django Admin Doc's and other older questions on stackoverflow on the subject it says I should be doing something along the lines of search_fields = ['inventoryitem__name'] but I think this is in reference to FK's in the same app model.py.
Is there a more correct way of referencing/importing other models from other apps that I'm missing or do I have to use some kind of callable method magic to get the search function to look up correctly? I've tried a multitude of different combinations and nothing seems to work. I'm relatively new to Django so I'm confident it's something simple.

You should use the double underscore notation to search a field on a related object. However, you should use the name of the foreign key field (e.g. recipe_name), not the name of the model (e.g. InventoryItem). It doesn't matter whether or not the foreign key's model is in the same app. For example:
search_fields = ['recipe_name__name']
Note that if you want to search the recipe_name and ingredient fields, you need to include both fields, even though they are foreign keys to the same model.
search_fields = ['recipe_name__name', 'ingredient__name']

Related

Django Rest Framework not validating all model's fields

I searched for this problem everywhere without being able to find an answer though it seems basic DRF usage, so I might be missing sth.
I have a Customer model with certain required fields:
from django.db import models
from django.utils.translation import gettext_lazy as _
from applications.core.models.country import Country
from applications.core.models.customer_states.customer_state import \
CustomerState
class Customer(models.Model):
class Meta:
verbose_name = _('customer')
verbose_name_plural = _('customers')
user_email = models.EmailField(_('email'), max_length=100, unique=True, default=None)
complete_name = models.CharField(_('complete name'), max_length=200, default=None)
phone = models.CharField(_('phone'), max_length=50, default=None)
country = models.ForeignKey(Country, models.PROTECT, verbose_name=_('country'), default=None)
city = models.CharField(_('city'), max_length=100, default=None)
city_state = models.CharField(_('city state'), max_length=100, default=None)
address = models.CharField(_('address'), max_length=100)
zip_code = models.CharField(_('zip code'), max_length=50, default=None)
customer_state = models.OneToOneField(CustomerState, models.PROTECT)
notes = models.TextField(_('notes'), max_length=200, blank=True, null=True)
And I have this serializer:
from rest_framework import serializers
from applications.core.models.customer import Customer
from applications.core.models.customer_states.implementations.pending_manual_validation_state import \
PendingManualValidationState
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = '__all__'
def to_internal_value(self, data):
self.add_default_state_if_missing(data)
return super(CustomerSerializer, self).to_internal_value(data)
#staticmethod
def add_default_state_if_missing(data):
data['customer_state'] = PendingManualValidationState.objects.create().pk
Though I have explicitly told DRF that it should use all model's fields it does not seem to check for the requirement of fields like 'address' and whenever I create the serializer with data missing 'address' and call serializer.is_valid() it returns True.
Why?
Found the answer by myself:
It would seem that default=None in the field makes DRF realize that even if the field is required, not receiving data for it is not a problem.
I set those default because otherwise Django would set a '' default value and thus, PostgreSQL would not raise exceptions for those empty data fields. But now that I am starting to use DRF for validations I no longer need those default.
Yet, if I happen to create and save a Customer without using the serializer, then I risk letting Django create a field with a default '' without having PostgreSQL complain about it. I figure that risk has a low probability though.

django checkbox select multiple models

Hi I have the following django model:
class Issue(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField(auto_now=True)
assignee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignee')
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner', null=True, blank=True)
description = models.TextField()
state = models.IntegerField(choices=STATUS_CHOICES, default=1)
priority = models.IntegerField(choices=RELEVANCE_CHOICES, default=2)
expired_date = models.DateField(auto_now=False, null=True, blank=True)
and a form which allow a user to create an Issue instance:
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title', 'description', 'assignee', 'state', 'priority', 'expired_date')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = "Titolo"
self.fields['description'].label = "Descrizione"
self.fields['state'].label = "Stato"
self.fields['priority'].label = "Priorità"
self.fields['expired_date'].label = "Termine"
self.fields['expired_date'].widget.attrs.update({'class': 'datepicker'})
self.fields['assignee'] = forms.MultipleChoiceField(
choices=self.fields['assignee'].choices,
widget=forms.CheckboxSelectMultiple,
label=("Assegnatario")
)
def clean(self):
cleaned_data = super().clean()
user_id = [i for i in cleaned_data['assignee']]
cleaned_data['assignee'] = [User.objects.get(id=i) for i in user_id]
return cleaned_data
I render this form and the field assignee is a checkbox.
I would like to be able to choose several assignee for the same issue, but I got an error because the Issue model expect just one User instance
How can I modify my model Issue in order to get more than one user ?
Thanks
you can create a new class and name it Issue_Instance where every Issue Object can have an assignee as a foreign key the problem that the relation is one to many because you have to choose more than one assignee and Django doesn't support the idea of having Array or List of Foreign Keys(I don't know any frame works that do :=) ) so I would suggest creating a new class or make the foreign key relation one-to-many key field read about it it will be very useful to solve your problem

Django Nested Form - Always Showing Object instead of model details

I'm working on a Django project generated via Mezzanine. I've been able to create my models, however I want to have a form where an admin can select from a list to assign a value in a many to many or a one to many relationship. For example, I have a model for Schemas:
class Schema(AutoCreatedUpdatedMixin, SoftDeleteMixin):
"""List of all Schemas in a given database"""
name = models.CharField(max_length=128, null=False)
status = models.BooleanField(max_length=128, null=False, default=True, verbose_name="Is Active")
description = models.CharField(max_length=65535, null=True, blank=True, default=None)
database = models.ForeignKey(Database, on_delete=models.CASCADE)
pull_requests = models.ManyToManyField(Link)
questions = models.ManyToManyField(Question, blank=True)
comments = models.ManyToManyField(Comment, blank=True)
technical_owners = models.ManyToManyField(Employee, related_name='technical_owners_schemas', blank=True)
business_owners = models.ManyToManyField(Employee, related_name='business_owners_schemas', blank=True)
watchers = models.ManyToManyField(Employee, related_name='watchers_schemas', blank=True)
def __unicode__(self):
return "{}".format(self.name)
And I have a model for Employees
class Employee(AutoCreatedUpdatedMixin, SoftDeleteMixin):
"""List of people with any involvement in tables or fields: business or technical owners, developers, etc"""
name = models.CharField(max_length=256, blank=False, null=False, default=None, unique=True)
email = models.EmailField(blank=True, null=True, unique=True)
def __unicode__(self):
return "{}".format(self.employee)
An employee can own multiple schemas and a schema can be owned by multiple employees. My database has an active employee in it, however when I try to create a Schema the employee shows up as Employee Object. Rather I would want the form to show the Employee.name. How can I do this? My admin file contains the following:
class SchemasAdmin(admin.ModelAdmin):
list_display = ['name', 'status', 'database', 'description']
ordering = ['status', 'database', 'name']
actions = []
exclude = ('created_at', 'updated_at', 'deleted_at')
First of all are you using python 2 or 3? For 3, the __str__ method should be used instead of __unicode__. I am writing this because it seems that there's a problem with the __unicode__ method of Employee, which although is defined as:
def __unicode__(self):
return "{}".format(self.employee)
th Employee class does not have an employee attribute (unless there's such an attribute in the mixins that class inherits from (AutoCreatedUpdatedMixin, SoftDeleteMixin) but I don't think that is the case.
In any case, the problem is that you haven't defined a propery __str__ (if using python 3) or __unicode__ (for python 2) method on the Employee class - just define it like:
return self.name
and you should see the employee's name in the django admin select fields.

Showing value (URL/Thumbnail) from Foreign key in Admin

I want to show an small preview image in Django Admin. I have made this hack, which works when the key is in the actual model itself.
class Product(models.Model):
prod_name = models.CharField ("Name", max_length=130)
image = models.URLField(max_length=340, blank=True, null=true)
def admin_image(self):
return '<center><img src="%s"/width="100px"></center>' %(self.image, self.image)
admin_image.allow_tags = True
However, I want it to show an image (read a URL) from a Foreign Key. I tried the following but no luck:
class Product_Option(models.Model):
colour = models.CharField (max_length=80, blank=True, null=True)
size = models.CharField (max_length=80, blank=True, null=True)
image_default = models.URLField(max_length=340, blank=True, null=True) # SHOW this image by
class Product(models.Model):
prod_name = models.CharField ("Name", max_length=130)
image = models.URLField(max_length=340, blank=True, null=true)
Default_Image = models.ForeignKey(Product_Option, blank=True, null= True)
Admin.py
class ProductAdmin(ImportExportModelAdmin):
resource_class = ProductResource
def admin_image(self, obj):
return '<center><img src="%s"/width="100px"></center>' %(obj.Stock_Image.image_default.url, obj.Stock_Image.image_default.url)
admin_image.allow_tags = True
list_display = ('prod_name','admin_image')
readonly_fields = ('admin_image',)
Your code is a little confusing, and you should be careful about putting HTML type code in your models. That being said, assuming you are trying to add thumbnails to your admin via foreignkey relations, this would be the easiest approach:
from django.utils.html import format_html
class ProductAdmin(ImportExportModelAdmin):
resource_class = ProductResource
list_display = ('prod_name', 'admin_image')
readonly_fields = ('admin_image',)
def admin_image(self, obj):
return format_html('<center><img src="{1}"/width="100px"></center>', obj.Default_Image.image_default, obj.Default_Image.image_default)
admin_image.allow_tags = True
Note: Notice the use of format_html(). Always use it in these cases to avoid vulnerabilities, as it escapes possibly malicious code.
Also, you were trying to use image_default.url, which only exists on an ImageField, not a URLField. I removed that as well in favor of just image_default.

Limit values in the modelformset field

I've been trying to solve this problem for a couple of days now, getting quite desperate. See the commented out code snippets for some of the things I've tried but didn't work.
Problem: How can I limit the values in the category field of the IngredientForm to only those belonging to the currently logged in user?
views.py
#login_required
def apphome(request):
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
# Attempt #1 (not working; error: 'IngredientFormFormSet' object has no attribute 'fields')
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.none())
# ingrformset.fields['category'].queryset = Category.objects.filter(user=request.user)
# Attempt #2 (doesn't work)
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.filter(category__user_id = request.user.id))
models.py:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
class Ingredient(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(Category, null=True, blank=True)
counter = models.IntegerField(default=0)
forms.py:
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'category')
UPDATE: I've made some progress but the solution is currently hard-coded and not really usable:
I found out I can control the categoryform field via form class and then pass the form in the view like this:
#forms.py
class IngredientForm(ModelForm):
category = forms.ModelChoiceField(queryset = Category.objects.filter(user_id = 1))
class Meta:
model = Ingredient
fields = ('name', 'category')
#views.py
IngrFormSet = modelformset_factory(Ingredient, form = IngredientForm, extra=1, fields=('name', 'category'))
The above produces the result I need but obviously the user is hardcoded. I need it to be dynamic (i.e. current user). I tried some solutions for accessing the request.user in forms.py but those didn't work.
Any ideas how to move forward?
You don't need any kind of custom forms. You can change the queryset of category field as:
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
IngrFormSet.form.base_fields['category'].queryset = Category.objects.filter(user__id=request.user.id)
Category.objects.filter(user=request.user)
returns a list object for the initial value in your form which makes little sense.
Try instead
Category.objects.get(user=request.user)
or
Category.objects.filter(user=request.user)[0]