I have a form just like this:
class addMeal(forms.Form):
name = forms.CharField(max_length=40,widget=forms.TextInput(attrs={'class':'form-control','placeholder':'نام وعده'}))
foods = forms.ModelMultipleChoiceField(queryset=Food.objects.filter(user=1),widget=forms.SelectMultiple(attrs={'class':'form-control'}))
class Meta:
model = Meals
i need write a queryset to get user id with request(see the queryset=Food.objects.filter(user=1) )
what should i do to fix it?
You override the constructor:
class MealForm(forms.ModelForm):
name = forms.CharField(
max_length=40,
widget=forms.TextInput(attrs={'class':'form-control','placeholder':'نام وعده'})
)
foods = forms.ModelMultipleChoiceField(
queryset=Food.objects.none(),
widget=forms.SelectMultiple(attrs={'class':'form-control'})
)
def __init__(self, *args, **kwargs, user=None):
super().__init__(*args, **kwargs)
if user is not None:
self.fields['foods'].queryset = Food.objects.filter(user=user)
class Meta:
model = Meals
In this case you should also inherit from ModelForm instead of Form. In the view, you then pass the user to the view:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
MealForm(user=request.user)
# …
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Related
How can I filter a queryset inside the Admin page of an object that has a ManyToManyField relation with a manually defined through model?
Given models.py
class Foo(models.Model):
foo_field1 = models.CharField(max_length=50)
class Main(models.Model):
main_field1 = models.CharField(max_length=50)
m2mfield = models.ManyToManyField(Foo, through="FooBar")
class FooBar(models.Model):
main = models.ForeignKey(Main, on_delete=models.CASCADE)
foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
new_field = models.CharField(max_length=50)
Inside admin.py
class M2MInlineAdmin(admin.TabularInline):
model = Main.m2mfield.through
extra = 1
class MainAdmin(admin.ModelAdmin):
inlines = [M2MInlineAdmin,]
...
def formfield_for_manytomany(self, db_field, request, **kwargs):
print('called formfield_for_manytomany')
return super().formfield_for_manytomany(db_field, request, **kwargs)
def get_field_queryset(self, db, db_field, request):
print('called get_field_queryset')
return super().get_field_queryset(db, db_field, request)
I try to access both of these methods, but none of them are called if I specify a through table. However, they do get called if the ManyToMany relation is simply defined as like this:
class Main(models.Model):
main_field1 = models.CharField(max_length=50)
m2mfield = models.ManyToManyField(Foo)
Is there a method to filter the queryset when a through table is specified (while being able to access the request context)?
EDIT:
The methods are indeed called when the ManyToManyField has a through model specified, only if there are no fieldsets specified inside the modelAdmin class.
How to access these methods when fieldsets are defined?
formfield_for_manytomany method seems to be called only when default form is used. When fieldsets is defined, it is using a different form which is why above method is not getting called.
Since you are using tabular admin for many to many field, you can override get_queryset to filter with field.
class M2MInlineAdmin(admin.TabularInline):
model = Main.fruits.through
extra = 1
def get_queryset(self, request):
qs = super(M2MInlineAdmin, self).get_queryset(request)
qs = qs.filter(some_arg=some_value)
return qs
Alternatively, you can write a custom model form and use it in admin instead of default form.
class MainAdminForm(forms.ModelForm):
class Meta:
model = Main
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# custom setup
class MainAdmin(admin.ModelAdmin):
form = MainAdminForm
You can use the formfield_for_foreignkey() method on the inline class.
class M2MInlineAdmin(admin.TabularInline):
model = Main.m2mfield.through
extra = 1
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
I need to get a FK info in logged User on ModelSerializer to add a new models.
In this case User->Business and Client->Business.
When post client I need to set Business id using the logged user Business.
It's important to say all other models have the same behavior. I'm looking for some generic solution for this problem.
Client Model
class Client(SoftDeletionModel):
object = ClientManager
business = models.ForeignKey(Business, related_name='business_clients', on_delete=models.CASCADE)
company_name = models.CharField(max_length=511, verbose_name=_('Company Name'))
cnpj = models.CharField(max_length=14, verbose_name=_('CNPJ'))
User Model
class User(AbstractUser):
"""User model."""
username = None
email = models.EmailField(_('email address'), unique=True)
business = models.ForeignKey(Business, related_name='business', on_delete=models.CASCADE, null=True)
ClientSerializer
class ClientSerializer(serializers.ModelSerializer):
business = serializers.IntegerField() # here how can I get user.business?
deleted_at = serializers.HiddenField(default=None)
active = serializers.BooleanField(read_only=True)
password = serializers.CharField(write_only=True, required=False, allow_blank=True)
password_contract = Base64PDFFileField()
class Meta:
model = Client
fields = '__all__'
validators = [
UniqueTogetherValidator2(
queryset=Client.objects.all(),
fields=('cnpj', 'business'),
message=_("CNPJ already exists"),
key_field_name='cnpj'
),
UniqueTogetherValidator2(
queryset=Client.objects.all(),
fields=('email', 'business'),
message=_("Email already exists"),
key_field_name='email'
)
]
Access request inside a serializer
Within the serializer you have access to the serializer context that can include the request instance
class ClientSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
return Client.objects.create(
business=self.context['request'].user.business,
**validated_data
)
Request is only acessible if you pass it when instantiate the serializer
Pass extra arguments to a serializer via save()
It is also possible to pass extra arguments to a serializer during the save() method call
def create(self, request, **kwargs)
serializer = ClientSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(business=request.user.business)
...
Create a mixin to set business
Finally, a more reusable way is create a mixin for views that provides create and/or update actions, then overwrite perform_create() and perform_update() methods
class BusinessMixin:
def perform_create(self, serializer):
serializer.save(business=self.request.user.business)
def perform_update(self, serializer):
serializer.save(business=self.request.user.business)
class ClientViewSet(BusinessMixin, ModelViewSet):
serializer_class = ClientSerializer
queryset = Client.objects.all()
...
ModelViewSet (basicallyCreateModelMixin and UpdateModelMixin) use these methods to call the save() method from serializer when executing its actions (create(), update() and partial_update(), i.e. POST, PUT and PATCH)
Inspired by serializers.CurrentUserDefault() magic I wrote CurrenUserBusinessDefault but set_context with current user business.
class CurrentUserBusinessDefault(object):
def set_context(self, serializer_field):
self.business = serializer_field.context['request'].user.business
def __call__(self):
return self.business
def __repr__(self):
return unicode_to_repr('%s()' % self.__class__.__name__)
So it's accessible like the default method
class ClientSerializer(serializers.ModelSerializer):
business = BusinessSerializer(default=CurrentUserBusinessDefault())
I cannot access current logged in user in Django class based view:
models.py:
class Userproject(models.Model):
class Meta:
verbose_name = u'pp'
verbose_name_plural = u'pps'
user = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name="project", verbose_name=_("Владелец проекта"))
#user = models.ForeignKey(User, unique=True)
name = models.TextField(u'Название проекта', unique=True)
date_created = models.DateTimeField(u'Дата создания',
default=datetime.now(), db_index=True)
date_until = models.DateTimeField(u'Оплачен по', default=datetime.now(), db_index=True)
views.py:
#login_required
class UserprojectList(ListView):
context_object_name = 'userproject_list'
queryset = Userproject.objects.filter(user=self.request.user)
template_name = 'userproject_list.html'
when i navigate to url i see error:
name 'self' is not defined
if i change self.request.user to request.user
the error is: name 'request' is not defined
Note that without user filtering view is working and shows data
django 1.8.5
You can just overwrite get_queryset:
#login_required
class UserprojectList(ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
def get_queryset(self):
return Userproject.objects.filter(user=self.request.user)
Also you can't use decorators on classes, so you have to write something like this:
from django.utils.decorators import method_decorator
class UserprojectList(ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UserprojectList, self).dispatch(*args, **kwargs)
def get_queryset(self):
return Userproject.objects.filter(user=self.request.user)
#pythad's answer is correct. But on Django 1.9+, instead of the dispatch method, you can use django.contrib.auth.mixins.LoginRequiredMixin to replace the old-style #login_required decorator.
from django.contrib.auth.mixins import LoginRequiredMixin
class UserprojectList(LoginRequiredMixin, ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
def get_queryset(self):
return Userproject.objects.filter(user=self.request.user)
I would try to do that in the __init__ method:
#login_required
class UserprojectList(ListView):
context_object_name = 'userproject_list'
template_name = 'userproject_list.html'
def __init__(self, *args, **kwargs):
super(UserprojectList, self).__init__(*args, **kwargs)
self.queryset = Userproject.objects.filter(user=self.request.user)
I think in the class-based views you would need to override the get_queryset() method in order to have access to the self.request object attached to the instance of the view rather than do this at the class level. The Classy Class-Based Views site has more information: http://ccbv.co.uk/projects/Django/1.8/django.views.generic.list/ListView/
Using Django 1.8 on Python 3.4.1 with models:
class Product(models.Model):
name = models.CharField(max_length=255)
# some more fields here
def __str__(self):
return self.name
class PricedProduct(models.Model):
product = models.ForeignKey(Product, related_name='prices')
# some more fields here
def __str__(self):
return str(self.product)
class Coming(models.Model):
# some unimportant fields here
class ComingProducts(models.Model):
coming = models.ForeignKey(Coming)
priced_product = models.ForeignKey(PricedProduct)
# more unimportant fields
and the following admin.py:
class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline):
model = ComingProducts
class ComingAdmin(admin.ModelAdmin):
inlines = [ComingProductsInline]
Of course, i have a problem with multiply queries to database: i have a query for each item in list and a query for each line. So, having 100 items i get 100 ^ 2 queries.
I've solved the problem with queries for each line with Caching queryset choices for ModelChoiceField or ModelMultipleChoiceField in a Django form
But i still having problem with str method. I've tried the following:
1) adding prefetch_related to ComingAdmin:
def get_queryset(self, request):
return super(ComingAdmin, self).get_queryset(request). \
prefetch_related('products__product')
2) adding select_related to ComingProductInline:
def get_queryset(self, request):
return super(ComingProductsInline, self).get_queryset(request). \
select_related('priced_product__product')
3) Defining custom form for inline and adding select_related to field queryset:
class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline):
model = ComingProducts
form = ComingProductsAdminForm
class ComingProductsAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ComingProductsAdminForm, self).__init__(args, kwargs)
self.fields['priced_product'].queryset = PricedProduct.objects.all(). \
select_related('product')
class Meta:
model = ComingProducts
fields = '__all__'
4) Defining a custom formset:
class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline):
model = ComingProducts
formset = MyInlineFormset
class MyInlineFormset(BaseInlineFormSet):
def __init__(self, data=None, files=None, instance=None,
save_as_new=False, prefix=None, queryset=None, **kwargs):
super(MyInlineFormset, self).__init__(data, files, instance,
save_as_new, prefix, queryset, **kwargs)
self.queryset = ComingProducts.objects.all(). \
prefetch_related('priced_product__product')
5) Different combinations for previous 4 methods
And nothing helps: each call of str for PricedProduct makes Django to perform a query for Product table. All of these methods were mentioned on stackoverflow, but they treated ModelAdmin, and do not help with Inline. What do i miss?
The formset solution does work for me, but with a slightly different approach:
class MyInlineFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(MyInlineFormset, self).__init__(*args, **kwargs)
self.queryset = self.queryset.prefetch_related('priced_product__product')
The BaseInlineFormSet class filters the queryset for you, and you need to take that filtered queryset and add the prefetch. With your formset implementation (the all() queryset) you get unrelated ComingProduct objects and it probably takes much too long to render. When it's the filtered queryset it renders very quickly.
You will find this approach very useful:
project/admin.py
from django.contrib import admin
from django.contrib.admin.options import BaseModelAdmin
from django.db.models.constants import LOOKUP_SEP
class AdminBaseWithSelectRelated(BaseModelAdmin):
"""
Admin Base using list_select_related for get_queryset related fields
"""
list_select_related = []
def get_queryset(self, request):
return super(AdminBaseWithSelectRelated, self).get_queryset(request).select_related(*self.list_select_related)
def form_apply_select_related(self, form):
for related_field in self.list_select_related:
splitted = related_field.split(LOOKUP_SEP)
if len(splitted) > 1:
field = splitted[0]
related = LOOKUP_SEP.join(splitted[1:])
form.base_fields[field].queryset = form.base_fields[field].queryset.select_related(related)
class AdminInlineWithSelectRelated(admin.TabularInline, AdminBaseWithSelectRelated):
"""
Admin Inline using list_select_related for get_queryset and get_formset related fields
"""
def get_formset(self, request, obj=None, **kwargs):
formset = super(AdminInlineWithSelectRelated, self).get_formset(request, obj, **kwargs)
self.form_apply_select_related(formset.form)
return formset
class AdminWithSelectRelated(admin.ModelAdmin, AdminBaseWithSelectRelated):
"""
Admin using list_select_related for get_queryset and get_form related fields
"""
def get_form(self, request, obj=None, **kwargs):
form = super(AdminWithSelectRelated, self).get_form(request, obj, **kwargs)
self.form_apply_select_related(form)
return form
class FilterWithSelectRelated(admin.RelatedFieldListFilter):
list_select_related = []
def field_choices(self, field, request, model_admin):
return [
(getattr(x, field.remote_field.get_related_field().attname), str(x))
for x in self.get_queryset(field)
]
def get_queryset(self, field):
return field.remote_field.model._default_manager.select_related(*self.list_select_related)
app/admin.py
from django.contrib import admin
from project.admin import AdminWithSelectRelated, AdminInlineWithSelectRelated, FilterWithSelectRelated
from .models import FormaPago, Comprobante, ItemServicio, ItemBazar
class ItemServicioInlineAdmin(AdminInlineWithSelectRelated):
model = ItemServicio
list_select_related = (
'alumno_servicio__alumno__estudiante__profile',
'alumno_servicio__servicio__grado',
'comprobante__forma_pago',
)
class ItemBazarInlineAdmin(AdminInlineWithSelectRelated):
model = ItemBazar
list_select_related = (
'alumno_item__alumno__estudiante__profile',
'alumno_item__item__anio_lectivo',
'comprobante__forma_pago',
)
class ComprobanteAdmin(AdminWithSelectRelated):
list_display = ('__str__', 'total', 'estado', 'fecha_generado', 'forma_pago', 'tipo', )
list_filter = ('estado', 'forma_pago', )
list_select_related = ('forma_pago', )
inlines = (ItemServicioInlineAdmin, ItemBazarInlineAdmin, )
class AlumnoFilter(FilterWithSelectRelated):
list_select_related = ('estudiante__profile', )
class ItemServicioAdmin(AdminWithSelectRelated):
list_display = ('nombre', 'alumno', 'monto_pagado', 'comprobante', )
list_filter = (
'alumno_servicio__alumno__seccion__grado',
('alumno_servicio__alumno', AlumnoFilter),
)
list_select_related = (
'comprobante__forma_pago',
'alumno_servicio__alumno__estudiante__profile',
'alumno_servicio__alumno__seccion__grado',
'alumno_servicio__servicio__grado',
)
class ItemBazarAdmin(AdminWithSelectRelated):
list_display = ('nombre', 'alumno', 'monto_pagado', 'comprobante', )
list_filter = (
'alumno_item__alumno__seccion__grado',
('alumno_item__alumno', AlumnoFilter),
)
list_select_related = (
'comprobante__forma_pago',
'alumno_item__alumno__estudiante__profile',
'alumno_item__alumno__seccion__grado',
'alumno_item__item__anio_lectivo',
)
admin.site.register(FormaPago)
admin.site.register(Comprobante, ComprobanteAdmin)
admin.site.register(ItemServicio, ItemServicioAdmin)
admin.site.register(ItemBazar, ItemBazarAdmin)
All I have to do is define the select_related fields, and the Custom AdminWithSelectRelated, AdminInlineWithSelectRelated, and FilterWithSelectRelated make use of them for Changelists, Changeforms, and even inline Formsets.
Works like a charm.
Inspired from #helpse answer you can also do the following if you just want to override the queryset for a single admin inline:
class ComingProductsInline(admin.TabularInline):
model = ComingProducts
def get_formset(self, request, obj=None, **kwargs):
formset = super(ComingProductsInline, self).get_formset(request, obj, **kwargs)
queryset = formset.form.base_fields["priced_product"].queryset
queryset = queryset.select_related("product")
formset.form.base_fields["priced_product"].queryset = queryset
return formset
It might be sufficient for most of the cases.
I'm working currently on a similar problem. What I have found is documented at this thread: Translatable Manytomany fields in admin generate many queries
One important observation I did is that my solution works only for Django 1.7x and not for 1.8. Exactly same code, with d1.7 I have order of 10^1 queries, and with new installation of d1.8 I have 10^4.
admin.py
class PromoAdmin(admin.modelAdmin)
list_display = ( 'name', 'id', 'category', 'promo_type', 'store', 'brand', 'date_start' )
form = SampleForm
forms.py
class SampleForm(forms.ModelForm):
class Meta:
model = Promo
def __init__(self, request *args, **kwargs):
super(PromoAdminForm, self).__init__(*args, **kwargs)
self.fields["store"].queryset = Store.objects.filter(onwer=request.user)
got an error on request
Django Version: 1.3.1
Exception Type: TypeError
Exception Value:
init() takes at least 2 arguments (1 given)
You cannot initiate the store field with request.user in the field declaration. You can try the following:
class MyAwesomeForm(forms.ModelForm):
store = forms.ModelChoiceField(Store.objects)
class Meta:
model = Promo
def __init__(self, user, *args, **kwargs):
super(MyAwesomeForm, self).__init__(*args, **kwargs)
self.fields['store'].queryset = Store.objects.filter(owner=user)
While instantiating the form you can pass the request.user object.
myform = MyAwesomeForm(request.user)
If you want to achieve this in the admin you might try this
For providing only the objects related to the logged-in user in the admin provides the possibility to overwrite ModelAdmin.queryset function:
class MyModelAdmin(admin.ModelAdmin):
form = MyAwesomeAdminForm()
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(store__owner=request.user)
class MyAwesomeAdminForm(forms.ModelForm):
class Meta:
model = Promo
Note that store__owner only works if you have a foreign key field stored in your promo model as such:
class Promo(models.Model):
store = models.ForeignKey(Store)
class Store(models.Model):
owner = models.ForeignKey(User)
I assume it should also be possible to somehow pass the request to the init method of the form. But did not find a suitable approach to do it.