How do you hide Django model fields with foreign keys? - django

I have a seminar that has several multiple choice questions.
Here is the seminar model:
class Seminar(models.Model):
seminar_name = models.CharField(max_length = 60)
is_active = models.BooleanField(default = True)
seminar_date = models.DateField(null=True, blank=True)
And here is the model for the multiple choice questions:
class Page(models.Model):
seminar = models.ForeignKey(Seminar)
multiple_choice_group = models.ForeignKey(MultipleChoiceGroup, null=True, blank=True, verbose_name="Multiple Choice Question")
page_id = models.IntegerField(verbose_name="Page ID")
I have the following in admin.py:
class PageInline(admin.StackedInline):
model = Page
extra = 0
def get_ordering(self, request):
return ['page_id']
# Limit the multiple choice questions that match the seminar that is being edited.
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "multiple_choice_group":
try:
parent_obj_id = request.resolver_match.args[0]
kwargs["queryset"] = MultipleChoiceGroup.objects.filter(seminar = parent_obj_id)
except IndexError:
pass
return super(PageInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
class SeminarAdmin(admin.ModelAdmin):
model = Seminar
inlines = (PageInline,)
list_display = ('seminar_name', 'is_active')
def get_ordering(self, request):
return ['seminar_name']
This is how the seminar appears:
Screenshot showing seminar fields
As shown in the screenshot, I'd like to hide some of the fields from the Seminar model and make some of them read-only.
Any help would be appreciated.

You can use the Model.Admin exclude and readonly_fields attributes. See documentation here and here.
For example:
class SeminarAdmin(admin.ModelAdmin):
model = Seminar
inlines = (PageInline,)
list_display = ('seminar_name', 'is_active')
def get_ordering(self, request):
return ['seminar_name']
exclude = ('seminar_name',)
readonly_fields = ('is_active',)

Related

Django Admin formfield_for_foreignkey for manytomany

My Model (simpliefied)
we have tours:
class Tour(models.Model):
name = models.CharField(max_length=100)
strecke = models.ManyToManyField(Strecke, through="Streckenreihenfolge")
A tour has sections:
class Strecke(models.Model):
name = models.CharField(max_length=100)
auftrag = models.ForeignKey("Auftrag", on_delete=models.CASCADE, null=True, blank=True)
And the sections are put in order
class Streckenreihenfolge(models.Model):
strecke = models.ForeignKey(Strecke, on_delete=models.CASCADE)
tour = models.ForeignKey("Tour", on_delete=models.CASCADE)
reihenfolge = models.IntegerField()
In my admin, I want to give some restrictions on which sections (Strecke) to show. I thought about using formfield_for_foreignkey. It gets called, but it doesn't have any impact on the options to select from:
#admin.register(Tour)
class TourAdmin(admin.ModelAdmin):
class StreckenreihenfolgeAdminInline(admin.TabularInline):
model = Streckenreihenfolge
autocomplete_fields = ["strecke"]
ordering = ["reihenfolge"]
extra = 0
def formfield_for_foreignkey(self, db_field, request, **kwargs):
print(db_field.name)
if db_field.name == 'strecke':
kwargs['queryset'] = Strecke.objects.filter(auftrag_id__exact=8)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
inlines = [StreckenreihenfolgeAdminInline, ]
Does formfield_for_foreignkey not work for manytomanyfields?
Update
Found some more infos here: Django Admin - Filter ManyToManyField with through model
Apparently formfield_for_manytomany doesn't work for inline forms.
I have then tried to use get_queryset(), which reduced the queryset, but somehow the autocomplete values are still unfiltered.
Maybe this image here is illustrating more what I'm try to achieve:
Finally found the answer here: https://forum.djangoproject.com/t/django-admin-autocomplete-field-search-customization/7455/5
Which results in the following code:
#admin.register(Strecke)
class StreckeAdmin(admin.ModelAdmin):
search_fields = ["name"]
def get_search_results(self, request, queryset, search_term):
print("get serach results")
queryset = queryset.filter(auftrag_id__exact=8)
qs = super().get_search_results(request, queryset, search_term)
print(qs)
return qs

Nested Multiple choice field serialize in Django and VueJS app

I have multiple choice nested serializer, I am able to get the value but during patch call subjects not getting updated
Here User and Subject are two models,
**Model.py**
class Subject(models.Model):
uid = models.AutoField(verbose_name='ID',
serialize=False,
auto_created=True,
primary_key=True)
ENG = "ENGLISH"
HND = "HINDI"
SUBJECT = (
(ENG, "English"),
(HND, "Hindi"),
)
subject = models.CharField(
max_length=50, choices=SUBJECT, default=ENG)
def __str__(self):
return self.subject
class User(AbstractUser):
uid = models.AutoField(verbose_name='ID',
serialize=False,
auto_created=True,
primary_key=True)
TEACHER = "Teacher"
STUDENT = "Student"
user_type = models.CharField(max_length=30, default=STUDENT)
approved = models.BooleanField(default=True)
def save(self, *args, **kwargs):
if self.user_type == User.TEACHER and self._state.adding:
self.approved = False
super().save(*args, **kwargs)
#property
def syllabus(self):
ret = self.teacher.syllabus_set.all()
if ret:
return ret
else:
return ''
Here is my serializer call
**serializers.py**
class TeacherProfileDetails(serializers.ModelSerializer):
logger = logging.getLogger(__name__)
teacher_date = AvailabilityDetails(many=True, read_only=True)
first_name = serializers.CharField(source='user.first_name', read_only=True)
last_name = serializers.CharField(source='user.last_name', read_only=True)
cities = CitySerializer(many=True, read_only=True)
subject = serializers.SerializerMethodField()
user = UserDetailsSerializer(read_only=True)
class Meta:
model = Teacher
fields = ('user', 'first_name', 'last_name',
'bio', 'teacher_cost', 'subject', 'teacher_date', 'cities')
def get_subject(self, obj):
subject_list = []
for i in obj.subject.all():
subject_list.append(i.subject)
return subject_list
Here is my views.py call
**views.py**
class TeacherListCreateAPIView(APIView):
logger = logging.getLogger(__name__)
#def create(self, request, *args, **kwargs):
def get(self, request, *args, **kwargs):
self.logger.info("Geeting TeacherListCreateAPIView information")
teacherList = Teacher.objects.filter(user__username=kwargs["username"])
self.logger.info(teacherList)
serializers = TeacherProfileDetails(teacherList, many=True)
self.logger.info(serializers.data)
return Response(serializers.data)
def patch(self, request, *args, **kwargs):
teacher = Teacher.objects.get(user__username=kwargs['username'])
serializers = TeacherProfileDetails(data=request.data, instance=teacher)
self.logger.info(serializers)
if serializers.is_valid():
serializers.save()
return Response(serializers.data, status=status.HTTP_201_CREATED)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)
Here is urls.py
path('teacher/<str:username>/details',
TeacherListCreateAPIView.as_view(), name="teacher-details"),
Issue :
I am able to get the teacher details, but patch call not updating subject, looks like I am doing some mistake in TeacherProfileDetails while serializing subject
SerializerMethodField is read-only. So, all fields in provided serializer are read-only.
You can use SlugRelatedField serializer instead, both for read and write:
subjects = serializers.SlugRelatedField(
many=True,
slug_field='subject',
queryset=Subject.objects.all(),
)
For write better to add logic in the view to make subjects contain unique elements or even allow adding one subject from patch to already present subjects.
Or you can use two separate serializers - one for read (current one), and one for write (with just fields allowed to be changed - it can be SlugRelatedField, or even just ListField with custom update() method logic, etc).

Multiple Queries For Inline instances Django admin

I have created a custom Admin for one of my model. And added a Relational table as one of its inline.
1). Now the thing is that Inline has over a 100 rows. And the inline further has a foreign key object to some other model.
2).Whenever i load the model form it takes a whole lot of time to Load.
3).I debugged and checked the code and number of queries. No query was duplicated and the count was nominal.
4).I have overridden the query set for the inline instance to prefetch its foreign key instance.
5).I went on and raised a random validation error in the formset's clean.
6).What i found out that when the error was raised it had a different query for every inline instance. Say if it has a 100 rows then the query was duplicated 100 times for that inline.
7).Is there any way to prefetch or optimize this scenario as its causing way too lag in my application
Here's the Code:
#model
class MyModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=10)
entity1 = models.ForeignKey("entities.entity1", null=True, blank=True, on_delete=models.CASCADE)
entity2 = models.ForeignKey("entities.entity2", null=True, blank=True, on_delete=models.CASCADE)
is_disabled = models.BooleanField(default=False)
name = models.CharField(max_length=24, blank=True, null=True)
class Meta:
db_table = 'my_model'
def __str__(self):
return "{}:{}".format(self.phone_number, self.entity2)
#admin
class Entity2Admin(admin.GeoModelAdmin, VersionAdmin):
list_filter = ('data_status')
readonly_fields = ('source', 'batch','is_live', )
exclude = ('search_key', 'live_at', 'qc_approved_at')
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if isinstance(instance, MyModel):
if (not instance.created_by):
instance.created_by = request.user
if (not instance.id):
instance.some_field = some_value
instance.save()
formset.save_m2m()
inlines = [MyModelInline]
extra_js = ['js/admin/GoogleMap.js','https://maps.googleapis.com/maps/api/js?key=AIzaSyA-5gVhxxxxxxxxxxxxxxxxxxxxxxx&callback=initGoogleMap']
#MyModelInline
class MyModelInline(admin.TabularInline):
model = MyModel
extra = 0
can_delete = True
show_change_link = False
formset = MyModelFormSet
readonly_fields = ['user']
verbose_name_plural = "MyModels"
fields = ['phone_number', 'name', 'user', 'entity2']
def get_queryset(self, request):
return super(MyModelInline, self).get_queryset(request).select_related('entity2', 'user')
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "entity2":
object_id = request.resolver_match.kwargs.get('object_id')
kwargs["queryset"] = Entity2.objects.filter(field=value)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Please help

Django: Add queryset to inlineformsets

I want to make a queryset on a field in an inline formset.. I have Inovice and Product models and InvoiceDetails model to link the manytomany relation between them.
here are the models:
class Invoices(models.Model):
"""The Invoice Creation Class."""
invoice_number = models.CharField(
_('invoice_number'), max_length=100, unique=True, null=True)
....
class Products(models.Model):
"""Product Creation Class."""
company = models.ForeignKey(Company, default=1)
barcode = models.CharField(_('barcode'), max_length=200, null=True)
....
class InvoiceDetail(models.Model):
invoice = models.ForeignKey(Invoices, related_name='parent_invoice')
product = models.ForeignKey(Products, related_name='parent_product')
quantity_sold = models.IntegerField(_('quantity_sold'))
...
when crearting an invoice i have inline formsets for the products which create an invoice details for every product.. now i want to filter the products that appear for the user to choose from them by the company. i searched a lot on how to override the queryset of inline formsets but found nothing useful for my case.
my forms:
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoices
fields = ('customer', 'invoice_due_date', 'discount', 'type')
def __init__(self, *args, **kwargs):
self.agent = kwargs.pop('agent')
super(InvoiceForm, self).__init__(*args, **kwargs)
def clean_customer(self):
.....
def clean(self):
......
class BaseDetailFormSet(forms.BaseInlineFormSet):
def clean(self):
......
DetailFormset = inlineformset_factory(Invoices,
InvoiceDetail,
fields=('product', 'quantity_sold'),
widgets= {'product': forms.Select(
attrs={
'class': 'search',
'data-live-search': 'true'
})},
formset=BaseDetailFormSet,
extra=1)
and use it in the views like that:
if request.method == 'POST':
invoice_form = InvoiceForm(
request.POST, request.FILES, agent=request.user)
detail_formset = DetailFormset(
request.POST)
.......
else:
invoice_form = InvoiceForm(agent=request.user)
detail_formset = DetailFormset()
so, how can it filter the products that show in detail_formset by company?
I solved it be passing the user to init and loop on forms to override the queryset.
def __init__(self, *args, **kwargs):
self.agent = kwargs.pop('agent')
super(BaseDetailFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.fields['product'].queryset = Products.objects.filter(company=self.agent.company)
in views:
detail_formset = DetailFormset(agent=request.user)

How to join models in Python djangorestframework

I am trying to joint two models in django-rest-framework.
My code isn't throwing any error but also it isn't showing other model fields that need to be joined.
Below is my code snippet:
Serializer:
class CompaniesSerializer(serializers.ModelSerializer):
class Meta:
model = Companies
fields = ('id', 'title', 'category')
class JobhistorySerializer(serializers.ModelSerializer):
companies = CompaniesSerializer(many=True,read_only=True)
class Meta:
model = Jobhistory
fields = ('id', 'title', 'company_id', 'companies')
View .
class UserJobs(generics.ListAPIView):
serializer_class = JobhistorySerializer()
def get_queryset(self):
user_id = self.kwargs['user_id']
data = Jobhistory.objects.filter(user_id=user_id)
return data
model:
class Companies(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100, blank=True, default='')
category = models.CharField(max_length=30, blank=True, default='')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
def save(self, *args, **kwargs):
title = self.title or False
category = self.category or False
super(Companies, self).save(*args, **kwargs)
class Jobhistory(models.Model):
id = models.AutoField(primary_key=True)
company_id = models.ForeignKey(Companies)
title = models.CharField(max_length=100, blank=True, default='')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
def save(self, *args, **kwargs):
company_id = self.company_id or False
title = self.title or False
super(Jobhistory, self).save(*args, **kwargs)
Thanks in advance. Any help will be appreciated.
In your views, you have
serializer_class = JobHistorySerializer()
Remove the parenthesis from this.
The reason for this is apparent in the GenericAPIView, specifically the get_serializer() and get_serializer_class() methods:
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
As you can see in get_serializer, it initializes that serializer class with args and kwargs that aren't provided in your view code.