I am having trouble getting my model manager to behave correctly when using the Admin interface. Basically, I have two models:
class Employee(models.Model):
objects = models.EmployeeManager()
username = models.CharField(max_length=45, primary_key=True)
. . .
class Eotm(models.Model): #Employee of the Month
date = models.DateField()
employee = models.ForeignKey(Employee)
. . .
And I have an EmployeeManager class that overrides the get() method, something like this:
class EmployeeManager(models.Manager):
use_for_related_fields = True
def get(self, *arguments, **keywords):
try:
return super(EmployeeManager, self).get(*arguments, **keywords)
except self.model.DoesNotExist:
#If there is no Employee matching query, try an LDAP lookup and create
#a model instance for the result, if there is one.
Basically, the idea is to have Employee objects automatically created from the information in Active Directory if they don't already exist in the database. This works well from my application code, but when I tried to create a Django admin page for the Eotm model, things weren't so nice. I replaced the default widget for ForeignKey fields with a TextInput widget so users could type a username (since username is the primary key). In theory, this should call EmployeeManager.get(username='whatever'), which would either return an Employee just like the default manager or create one and return it if one didn't already exist. The problem is, my manager is not being used.
I can't find anything in the Django documentation about using custom Manager classes and the Admin site, aside from the generic manager documentation. I did find a blog entry that talked about specifying a custom manager for ModelAdmin classes, but that doesn't really help because I don't want to change the model represented by a ModelAdmin class, but one to which it is related.
I may not be understanding what you're trying to do here, but you could use a custom Form for your Eotm model:
#admin.py
from forms import EotmAdminForm
class EotmAdmin(models.ModelAdmin):
form = EotmAdminForm
#forms.py
from django import forms
from models import Eotm, Employee
class EotmAdminForm(forms.ModelForm)
class Meta:
model = Eotm
def clean_employee(self):
username = self.cleaned_data['employee']
return Employee.get(username=username)
That, in theory, should work. I haven't tested it.
Related
I have a Project model
class Project(models.Model)
name = models.CharField(max_length=255)
members = models.ManyToManyField(User, related_name="members")
and I am using a classbased Update view to restrict only those users who are members of the project.
class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Project
fields = ["name"]
def test_func(self):
members = self.get_object().members.all()
if members.contains(self.request.user):
return True
return False
After looking at the SQL queries through Djang Debug Toolbar, I see the query is duplicated 2 times.
the 1st instance is from this test_func and the other instance is from django generic views, how can I resolve the duplicated query issue
Just filter the queryset to retrieve only projects where the request.user is a member of that Project, so:
class ProjectUpdateView(LoginRequiredMixin, UpdateView):
model = Project
fields = ['name']
def get_querset(self):
return Project.objects.filter(members=self.request.user)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the Project
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the members relation to projects.
I have two objects that are connected together by a ForeignKey.
class Question(models.Model):
text = models.Charfield()
class AnswerOption(models.Model):
text = models.Charfield()
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="options")
When Question is created in the admin interface I'm using a Inline form for AnswerOptions so that they can be created at the same time. I'd like to perform some validation on the Question and it needs access to the new AnswerOptions to make the decision.
I've added a clean method to Question but the options property is empty.
What is the correct way to validate Question?
[EDIT]
Made it clear that Question needs access to the AnswerOptions to be able to validate everything.
[EDIT]
Added explicit reference to using an InlineForm for AnswerOptions in the admin interface.
I'd do this through a Django form, which have a more robust interface for
validation. The clean method on your form is the place for this type
of validation.
# forms.py
from django import forms
from .models import Question
class QuestionForm(forms.Form):
text = models.Charfield()
class Meta:
model = Question
def clean(self):
options = self.cleaned_data['options']
if not option.are_ok:
raise forms.ValidationError
# admin.py
from django import admin
from .forms import QuestionForm
class QuestionAdmin(admin.ModelAdmin):
form = QuestionForm
...
From the docs:
The form subclass’s clean() method can perform validation that requires access to multiple form fields. This is where you might put in checks such as “if field A is supplied, field B must contain a valid email address”. This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.
This is what I've discovered:
When creating Inline forms in the admin interface Django creates a Formset to handle the multiple forms. (The example here is the same as my use case)
Formsets have a clean() method like other forms and they have a forms property to access the child forms.
Just like normal forms they have an instance property that refers to the 'base' class and the individual forms have an instance property that gets you a instance of the newly submitted data.
Putting it all together:
# models.py
class Question(models.Model):
text = models.Charfield()
class AnswerOption(models.Model):
text = models.Charfield()
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="options")
# admin.py
from django.contrib import admin
from django.forms.models import BaseInlineFormSet
class AnswerOptionFormset(BaseInlineFormset):
def clean(self):
super().clean() # See note in docs about calling this to check unique constraints
#self.instance -> Question, with all the newly submitted, and validated, data.
#self.forms -> iterator over all the submitted AnswerOption forms
#for f in self.forms:
# f.instance -> instance of AnswerOption containing the new validated data
#Note: self.instance.options will refer to the previous AnswerOptions
#raise ValidationError for anything that is wrong.
#It is also possible to modify the data in self.instance or form.instance instead.
class AnswerOptionInline(admin.TabularInline):
formset = AnswerOptionFormset # note formset on AnswerOption NOT QuestionAdmin
class QuestionAdmin(admin.ModelAdmin):
inlines = [AnswerOptionInline]
Obviously I am new to Django, because I would assume this is relatively simple.
Lets say in my models.py I created a model "User", with two fields, a "username" and a "email" field. In a form called "UserForm", I want to access a list of all the "username"s in the "User" model. This list would then be used to populate a dropdown menu using Select.
I feel like this should be really easy, and I have been looking for some simple way to do it. I can find lots of ways that aren't all inclusive (ie filter(username = "Joe")), but I can't find one that will list all the users.
Any help would be greatly appreciated.
You're looking for a ModelChoiceField. Its queryset property can be populated from an ORM call, getting you all of the Users. Have a look at the section Creating Forms from Models in the docs for more information.
from django.contrib.auth.models import User
class UserForm(forms.ModelForm):
class Meta:
model = # Your model here
def __init__(self, *args, **kwargs):
self.fields['user'] = forms.ModelChoiceField(queryset=User.objects.all())
ForeignKey fields will be automatically shown as ModelChoiceFields, but you can always override the choices if you need.
Initially, I started my UserProfile like this:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
verified = models.BooleanField()
mobile = models.CharField(max_length=32)
def __unicode__(self):
return self.user.email
Which works nicely along with AUTH_PROFILE_MODULE = 'accounts.UserProfile' set in settings.py.
However, I have two different kinds of users in my website, Individuals and Corporate, each having their own unique attributes. For instance, I would want my Individual users to have a single user only, hence having user = models.OneToOneField(User), and for Corporate I would want them to have multiple users related to the same profile, so I would have user = models.ForeignKey(User) instead.
So I thought about segregating the model into two different models, IndivProfile and CorpProfile, both inheriting from UserProfile while moving the model-specific attributes into the relevant sub-models. Seems like a good idea to me and would probably work, however I would not be able to specify AUTH_PROFILE_MODULE this way since I'm having two user profiles that would be different for different users.
I also thought about doing it the other way around, having UserProfile inherit from multiple classes (models), something like this:
class UserProfile(IndivProfile, CorpProfile):
# some field
def __unicode__(self):
return self.user.email
This way I would set AUTH_PROFILE_MODULE = 'accounts.UserProfile' and solve its problem. But that doesn't look like it's going to work, since inheritance in python works from left to right and all the variables in IndivProfile will be dominant.
Sure I can always have one single model with IndivProfile and CorpProfile variables all mixed in together and then I would use the required ones where necessary. But that is just doesn't look clean to me, I would rather have them segregated and use the appropriate model in the appropriate place.
Any suggestions of a clean way of doing this?
You can do this in following way. Have a profile which will contains common fields which are necessary in both profiles. And you have already done this by creating class UserProfile.
class UserProfile(models.Model):
user = models.ForeignKey(User)
# Some common fields here, which are shared among both corporate and individual profiles
class CorporateUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Corporate fields here
class Meta:
db_table = 'corporate_user'
class IndividualUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Individual user fields here
class Meta:
db_table = 'individual_user'
There is no rocket science involved here. Just have a keyword which will distinguish between corporate profile or individual profile. E.g. Consider that the user is signing up. Then have a field on form which will differentiate whether the user is signing up for corporate or not. And Use that keyword(request parameter) to save the user in respective model.
Then later on when ever you want to check that the profile of user is corporate or individual you can check it by writing a small function.
def is_corporate_profile(profile):
try:
profile.corporate_user
return True
except CorporateUser.DoesNotExist:
return False
# If there is no corporate profile is associated with main profile then it will raise `DoesNotExist` exception and it means its individual profile
# You can use this function as a template function also to use in template
{% if profile|is_corporate_profile %}
Hope this will lead you some where. Thanks!
I have done it this way.
PROFILE_TYPES = (
(u'INDV', 'Individual'),
(u'CORP', 'Corporate'),
)
# used just to define the relation between User and Profile
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey('Profile')
type = models.CharField(choices=PROFILE_TYPES, max_length=16)
# common fields reside here
class Profile(models.Model):
verified = models.BooleanField(default=False)
I ended up using an intermediate table to reflect the relation between two abstract models, User which is already defined in Django, and my Profile model. In case of having attributes that are not common, I will create a new model and relate it to Profile.
Could be worth to try using a through field. The idea behind it is to use the UserProfile model as through model for the CorpProfile or IndivProfile models. That way it is being created as soon as a Corp or Indiv Profile is linked to a user:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey(Profile, related_name='special_profile')
class Profile(models.Model):
common_property=something
class CorpProfile(Profile):
user=models.ForeignKey(User, through=UserProfile)
corp_property1=someproperty1
corp_property2=someproperty2
class IndivProfile(Profile):
user=models.ForeignKey(User, through=UserProfile, unique=true)
indiv_property1=something
indiv_property2=something
I think that way it should be possible to set AUTH_PROFILE_MODULE = 'accounts.UserProfile', and every time you create either a CorpProfile or a IndivProfile that is linked to a real user a unique UserProfile model is created. You can then access that with db queries or whatever you want.
I haven't tested this, so no guarantees. It may be a little bit hacky, but on the other side i find the idea quite appealing. :)
I extended the User model in Django by using Profiles. As you know, creating a new user doesn't create a new profile linked to that user, so we need to add an auxiliary method, something like this:
class UserProfile(models.Model):
user = models.OneToOneField(User)
field_to_be_updated = models.DateTimeField(auto_now_add=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u))
That works great, but I'd like to be able to call a method, say, update_a_field within the UserProfile class in such a way I could do User.objects.all[0].profile.update_a_field() or something to that effect.
I have tried to create a Manager to UserProfile class to no avail. It just doesn't recognize the methods I attach to the class. It seems UserProfile is not a regular model in the sense it ccan't be accesed by UserProfile.objects...
So, how can I add methods to update a UserProfile instance? Should I add a method to the User model and then access UserProfile in that way?.
I'm not sure exactly why but my experience is:
DOES NOT WORK
user.profile.field = "value"
user.profile.save()
DOES WORK
profile = user.profile
profile.field = "value"
profile.save()