Django admin - Limit the choices in dropdown - python

I am starting to use Django and I have an issue trying to filter data based on user information. Let me explain how my app works. A User in my app belongs to a company. So I created a table called Company to capture company info. Then I created another table called UserCompany. Basically, it stores the Id from the Django User and the Id from my company table. Now, I want to filter data so that the user will see results filtered in the Django Admin based on their company id. He/She can only see data based on their company Id. I was able to figure this user out using get_queryset in admin.py. My only issue now is, the dropdown list that shows in admin as a result of foreign keys isn't being filtered. I did some research and found out about limit_choices_to. I can set that statically like this:
class Cleaner(models.Model):
company = models.ForeignKey('Company',limit_choices_to = {'companyname' = 'Test'}
The dropdown list in the admin section only shows the company Test. How can I do this dynamically? Do I do it in the model or do I do it in admin.py? Please help!

It's time to setup CustomAdmin for your models now. You will have to override formfield_for_foreignkey method of ModelAdmin
class CleanerAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == "company":
kwargs["queryset"] = Company.objects.filter(name='Test')
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
admin.site.register(Cleaner, CleanerAdmin)
If there are session specific filter for which you need reference to logged in user object you also have reference to request object. You can write any logic here.

#admin.register(Cleaner)
class CleanerAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == "company":
kwargs["queryset"] = db_field.related_model.objects.filter(name='Test')
return super(CleanerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

Related

Django Admin Form: Set the default value of a readonly field

THE GOAL: Display a request-specific, read-only user_id on a django admin form when creating and updating a resource.
This display should be a readonly field (readonly, NOT disabled). The user_id displayed is derived from the requesting user (request.user) so the initial value is set from that.
Following this post, we should simply set it in the get_form method like so:
def get_form(self, request, obj=None, *args, **kwargs):
form = super(ProfileAdmin, self).get_form(request, *args, **kwargs)
form.base_fields['user'].initial = request.user
return form
However, user is no longer on the base_fields when it is readonly. In fact, I can't find it anywhere. Any thoughts?
Other posts suggest on save, which I intend to do, but I need it to show on the form before then.
You are right that making the field readonly excludes it from the base_fields.As mentioned in the docs :
Any fields in this option (which should be a list or tuple) will display its data as-is and non-editable; they are also excluded from the ModelForm used for creating and editing.
You could have define a method for it just like list_display but since you need to set user from the request, we need to have the request object, which isn't available in the method.
So for your use case, we can use formfield_for_foreignkey method as :
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'lend_by':
# setting the user from the request object
kwargs['initial'] = request.user.id
# making the field readonly
kwargs['disabled'] = True
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Hope it helps.

pass parameter from CreateView to template (Django)

I've seen a similar question, alas it has not been answered.
I have an app that features Entries (like blog entries) which include a part called SubEntry. I want the users to be able to report SubEntries (i.e. press the button 'report', fill some fields and the application sends an email to admins, saving the report in db is nice to have):
The flow should be like that: at the view EntryDetails (url: /entry/entry-title/) the user may click on the SubEntry part. The modal opens and the subentry is visualized in the modal as enlarged, with a button/link underneath 'Report the SubEntry'. Then it's possible to click on the 'Report the SubEntry' button and two fields appear - reason of reporting and contact detail of the reporter (here I am just toggling the visibility of the fields). I manage to display the form (with get overriden - overriding get_form_kwargs causes the error No Entry with that title) but either the Entry or its attributes are not displayed...
My questions are:
1) is creating a model for Reporting (ReportSubEntry) a decent approach?
2) I can't seem to pass the needed variable (an Entry object that is to be a ForeignKey for a SubEntry object that is being created) from CreateReport view to the report_subentry.html.
any thoughts, advice? Python 3.5, Django 1.10
models.py:
class ReportSubentry(models.Model):
Entry = models.ForeignKey('Entry')
details = models.CharField(max_length=100)
contact = models.EmailField()
forms.py:
class ReportEntryForm(forms.ModelForm):
class Meta:
model = ReportSubEntry
fields = ['details', 'contact', 'project']
views.py:
class CreateReport(CreateView):
model = ReportSubEntry
form_class = ReportSubEntryForm
template_name = 'understand/report_subentry.html'
# tried two methods to pass the variables:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return super(CreateReport, self).get(request, **kwargs)
def get_form_kwargs(self, **kwargs):
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return kwargs
The current model that you are using ReportSubEntry is perfect and there is no need to change it.
In your forms.py ReportEntryForm you have to use relatedfields to be able to correctly serialize the data. There is no need to override anything. When user clicks on report the sub entry you have to pass the pk of Entry model as it is required to know which entry is reported. I am assuming that since you are successfully displaying the entries pk of those are present. When you receive the pk with other two fields you get the corresponding entry for pk and then pass the object to ReportSubentry.objects.create method.
The reportentry form should not contain foreign key. You have two choices for that. First is remove that field and pass the pk of entry from frontend using ajax calls or use javascript to add a disabled input field which contains pk of entry when user clicks on report subentry.
Ok, so I've solved this issue.
The only solution that worked for me was overriding the get method of the ReportSubentry without calling the get method of the superclass:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
entry = get_object_or_404(Entry, title=title)
context_data = self.get_context_data()
context_data.update(entry=entry)
return self.render_to_response(context_data)
Please feel free to discuss it.

Django validate what user sends from admin panel

I am kind of new to Django, and i am trying to make sort of a news website where users can submit articles(with an account) but the admin needs to check them before they can be posted. Is that possible?
Yes, it is.
The simplest approach would be creating simple flag in model let's say a Boolean field named verified, which by default would be False. You could add permissions. So in the end you could overwrite a function in your admin form, and show the field for superuser only.
class MyUserAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.exclude = []
if not request.user.is_superuser:
self.exclude.append('Permissions') #here!
return super(MyUserAdmin, self).get_form(request, obj, **kwargs)

Dynamic class based on database query in Python (Django)

I'd like to do something like this in Django:
class MyForm(forms.Form):
items = Items.objects.all()
for item in items:
# How does this part work?
exec(item.name) = forms.BooleanField()
The goal is to create one form field for each item returned from the database query. So, if I get ten items back from the query, then the class would have ten variables in it, each named after a returned item.
This seems theoretically possible, but is there some danger here? The items in the database are not user generated.
You can dynamically modify a form however you please:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
for item in Item.objects.all():
self.fields[item.name] = forms.BooleanField()
I suppose the danger is if the database state changes while a user is submitting a form, and the new form initializes with new fields which the previous form didn't have.
if I was you I would have used django formsets.
Why formsets were not an option here ?

django admin - populate field with callable

I can't find a single example of anyone doing this apart from this example, which doesn't help me other than to know where the code needs to sit.
How to prepopulate UserProfile fields in the Django admin?
so this is my code
class QuoteMaterial(models.Model):
name = models.CharField(_('name'), max_length=255)
content = models.TextField(_('content'),
help_text=_('A static priced item used when doing a job. Selectable when creating a quote. '))
price = models.DecimalField(_('price'), max_digits=6, help_text="not sure if this is before or after VAT yet", decimal_places=2, default="0.00")
def get_companies():
return CompanyProfile.objects.filter(user=request.user)
company = models.ForeignKey(CompanyProfile, default=get_companies)
If its not obvious, im trying in the admin section to populate a dropdown with the available companies that belong to the user that is logged in.
my problem is that i dont know how to pass the request object to "get_companies". anyone know of any examples.
You will have to do this overriding in your admin class that extends the ModelAdmin, not in your class that extends models.Model. Specifically, you need to override formfield_for_foreignkey.
From the docs:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
For your case, it would seem like:
if db_field.name == "company":
kwargs['queryset'] = request.user.company_set.all()
You're mixing up terms.
"Prepopulating" means to fill in a field from another field. It's not how you filter things for the admin popups, since you aren't actually setting the field, but simply limiting choices and letting the user set the field from those.
Aditionally, the default value for a field needs to be a constant, since this is passed down to the database, which can't use a query to set a default.
What you really want is something like the limit_choices_to (docs) parameter for your ForeignKey, but even then, you can't use request for this; it has to work using fields in the model. The reason for this is that, if you based it on the user, then some users would be unable to select the current value set by another user. You don't want company changing itself when the user just wants to change content, for example, just because user doesn't yield the current company in the filter.

Categories

Resources