So I've a formset tied to a model and one of the fields in that is ForeignKey.
models.py
class Squad(models.Model):
rid = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
def __str__(self):
return self.team.tname
forms.py
class SquadForm(ModelForm):
class Meta:
model = Squad
def __init__(self, logged_user, user, *args, **kwargs):
super(SquadForm, self).__init__(*args, **kwargs)
self.fields['team'] = forms.ModelChoiceField(queryset=Team.rows.get_my_teams(user=logged_user), empty_label="None")
As you can see, the __init__ function is expecting an extra parameter logged_user which I'm hoping to pass via the views.py file. But if I do the following:
views.py
def choose_teams(request):
teamformset = modelformset_factory(Squad, extra=2, form=SquadForm(request.user))
form = teamformset(queryset=Squad.objects.none())
return render(request, 'foo.html', {'form':form})
I'm trying to pass the logged in user as a parameter on line 2 but this is resulting in the following message:
Field 'id' expected a number but got 'SquadForm'
Not sure what I'm missing here. But if I remove the parameter from line 2:
teamformset = modelformset_factory(Squad, extra=series.team_number, form=SquadForm)
it starts working (of course, I no longer expect the user in the forms.py file and remove it too) but shows all the data and not filtered one.
You can pass additional keyword arguments to your formset form by passing form_kwargs={} to your formset
class SquadForm(ModelForm):
class Meta:
model = Squad
def __init__(self, *args, logged_user, **kwargs):
super(SquadForm, self).__init__(*args, **kwargs)
self.fields['team'] = forms.ModelChoiceField(queryset=Team.rows.get_my_teams(user=logged_user), empty_label="None")
teamformset = modelformset_factory(Squad, extra=2, form=SquadForm)
form = teamformset(queryset=Squad.objects.none(), form_kwargs={'logged_user': request.user})
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)
Can Anyone please explain me what this error means??
I have done this in my views.py:
class FormListView(FormMixin, ListView):
def get(self, request, *args, **kwargs):
# From ProcessFormMixin
form_class = self.get_form_class()
self.form = self.get_form(form_class)
# From BaseListView
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty and len(self.object_list) == 0:
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
context = self.get_context_data(object_list=self.object_list, form=self.form)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
class CompanyListView(LoginRequiredMixin,FormListView):
model = Company
form_class = daterangeform
paginate_by = 10
def get_queryset(self):
return company.objects.filter(User=self.request.user)
def get_context_data(self, **kwargs):
context = super(companyListView, self).get_context_data(**kwargs)
context['selectdate_list'] = selectdate.objects.filter(User=self.request.user).latest()
return context
And I am getting this error:
ValueError: earliest() and latest() require either fields as positional arguments or 'get_latest_by' in the model's Meta.
Can anyone please explain me what is wrong in my code and possible solution for doing it in correct way...
Thank you
As specified in the documentation for latest(*fields) [Django-doc]:
Returns the latest object in the table based on the given
field(s).
This example returns the latest Entry in the table, according to the
pub_date field:
Entry.objects.latest('pub_date')
So if you want to obtain the latest object with respect to a field (for example updated_date), you can write this as:
selectdate.objects.filter(
User=self.request.user
).latest('updated_date')
You can however use the latest() without parameters, given you specified the order for this in the Meta class of the model, like:
class Foo(models.Model):
name = models.CharField(max_length=20)
class Meta:
get_latest_by = ['name']
In that case
Foo.objects.latest()
will give the Foo object with the maximum name (if we here compare the names lexicographically).
Many toys have same information(for example description and price) how can i customise my admin.py in order to get last saved object values, everytime when i press add new toy in admin panel and display them in my input fields.
models.py
class Toy(models.Model):
name = models.CharField(max_length=255)
description = TextField()
quantity = models.FloatField(default=0.0)
price = models.FloatField()
def __unicode__(self):
return self.name
admin.py
admin.site.register(Toy)
Try to do it like that:
First create a form in your admin.py:
class ToyForm(forms.ModelForm):
class Meta:
model = Toy
fields = ('__all__')
def __init__(self, *args, **kwargs):
#if not an edit
if 'instance' not in kwargs:
#we get the last object
last_object = Toy.objects.all().order_by("id")[0]
#we add the last object informations to the initial data
initial = {'description': last_object.description, 'price': last_object.price}
kwargs['initial'] = initial
super(ToyForm, self).__init__(*args, **kwargs)
Then add this form to the Admin model:
class ToyAdmin(admin.ModelAdmin):
form = ToyForm
Fianally:
admin.site.register(ToyAdmin, Toy)
In modeladmin class there is a method get_form, you can override the method to return, the values returned from you conditioned queryset results ( Mymodel.objects.last() )
from django.contrib import admin
#admin.register(Toy)
class ToyAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(ToyAdmin, self).get_form(request, obj, **kwargs)
# code logic to check condition & queryset like Toy.objects.last()
form.base_fields['name'].initial = 'value'
return form
I do not want the logged in user to show up on this ModelMultipleChoiceField in order to restrict themselves from creating a following relationship with themselves? So how do I exclude the logged in user from the queryset, probably an easy fix but I'm new to Django and it has eluded me for a few hours now.
forms.py
class Add_Profile(forms.ModelForm):
def __init__(self,*args, **kwargs): # initializing your form in other words loading it
super(Add_Profile, self).__init__(*args, **kwargs)
user_id = kwargs.pop('user_id') # taking user_id out of the querylist
self.fields['follows'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=UserProfile.objects.filter(~Q(id=user_id)))
class Meta:
model = UserProfile
fields = (
'bio',
'follows',
'theme',
'profile_picture',
)
Views.py
#login_required
def edit_profile(request, user_id):
userprofile = UserProfile.objects.get(pk=user_id)
if request.method == 'POST':
edit_profile = Add_Profile(request.POST, request.FILES, instance=userprofile, user_id=request.user.id)
if edit_profile.is_valid():
edit_profile.save()
return redirect('/home/user/{0}/'.format(request.user.username))
else:
print edit_profile.errors
else:
edit_profile = Add_Profile(instance=userprofile, user_id=request.user.id)
return render (request, 'edit.html', {'form': edit_profile,})
Error: init() got an unexpected keyword argument 'user_id'
You can definitely do it using forms.Form instead of forms.ModelForm with something along the lines of this example in the docs:
from django import forms
from django.contrib.auth import get_user_model
class Add_Profile(forms.Form):
follows = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, user=None, *args, **kwargs):
super(Add_Profile, self).__init__(*args, **kwargs)
if user is not None:
self.fields['follows'].queryset = get_user_model().objects.exclude(pk=user.pk)
else:
self.fields['follows'].queryset = get_user_model.objects.all()
Just pass in the user you wish to exclude when you instantiate the form:
form = Add_Profile() # all users will be present in the dropdown
some_guy = User.objects.get(pk=4)
form = Add_Profile(user=some_guy) # all users except some_guy will be present
Define an __init__ method for the form class. Pass the logged in userid to the form while initializing it, this will work with a model form.
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id')
super(Add_Profile, self).__init__(*args, **kwargs)
self.fields['follows'] = forms.ModelMultipleChoiceField(queryset=UserProfile.objects.filter(~Q(user_id=user_id)))
While initializing your form, you can pass user_id
address_form = Add_Profile(request.POST, user_id=request.user.id)
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.