I am relatively new to Django and I'm trying to achieve something that is not quite clear in the documentation. My application requires multiple types of users. So, I have extended django User, creating a Profile model that contains additional fields common in all User types:
USER_TYPES = (
('A', 'UserTypeA'),
('B', 'UserTypeB'),
('C', 'UserTypeC'),
)
class Profile(models.Model):
user = models.OneToOneField(User, unique=True)
about = models.TextField(blank=True)
user_type = models.CharField(max_length=3, choices=USER_TYPES, default='A')
def save(self, *args, **kwargs):
try:
existing = Profile.objects.get(user=self.user)
self.id = existing.id #force update instead of insert
except Profile.DoesNotExist:
print "Profile not created yet"
models.Model.save(self, *args, **kwargs)
def create_user(sender, instance, created, **kwargs):
print "User Profile Creation: False"
if created:
print "User Profile Creation: ", created
Profile.objects.create(user=instance)
post_save.connect(create_user, sender=Profile)
In settings.py I have set:
AUTH_PROFILE_MODULE = 'users.Profile'
After that i have defined my UserTypeX models deriving from Profile models like this:
class UserTypeA(Profile):
phone_number = models.CharField(max_length=13,blank=False)
class UserTypeB(Profile):
company_name = models.CharField(max_length=30,blank=False)
phone_number = models.CharField(max_length=13,blank=False)
def __init__(self, *args, **kwargs):
kwargs['user_type'] = 'B'
super(UserTypeB, self).__init__(*args, **kwargs)
...
I've registered those user models to admin so that I could manage my users independently.
The default admin behavior displays correctly all the Profile and UserTypeX fields and there is a select box (with a plus button next to it) for the User field - due to the OneToOneField relationship between Profile and User models. So whenever I want to create a new UserTypeX, I have to press the plus button and fill in a new popup window all the default User django defined fields.
What I am struggling to do now is display those User fields, not in a new popup window but inline in my UserTypeX add/edit page. I've read the documentation about StackedInlines and TabularInlines but those doesn't fit my case because I want to inline parent fields in an ancestor's add/edit page and not vice versa.
Is there any suggested solution with example code (please!) for that problem? Thank's in advance!
So, to make things short, is there a way to display User fields (instead of the select/add functionality due to OneToOneField relationship) in Profile add/edit screen in admin?
Update: A related question (unanswered though...) that briefly addresses the problem is:
Reverse Inlines in Django Admin
No, you cannot. But you can make B inline in A if you want. Or maybe you can manipulate how django display it in
def unicode(self):
models.py
class A(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class B(models.Model):
name = models.CharField(max_length=50)
a = models.ForeignKey(A)
admin.py
class B_Inline(admin.TabularInline):
model = B
class A_Admin(admin.ModelAdmin):
inlines = [
B_Inline,
]
admin.site.register(A, A_Admin)
admin.site.register(B)
Or maybe you want to use many-to-many relationship?
models.py
class C(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class D(models.Model):
name = models.CharField(max_length=50)
cs = models.ManyToManyField(C)
admin.py
class C_Inline(admin.TabularInline):
model = D.cs.through
class D_Admin(admin.ModelAdmin):
exclude = ("cs",)
inlines = [
C_Inline,
]
admin.site.register(C)
admin.site.register(D, D_Admin)
I don't see how this is going to work - you have told django to use users.Profile as your profile model but you actually want to use it's children. Plus as you say you're wanting to use the inline forms the "wrong way around". Without knowing more about what exactly you're trying to achieve I'd suggest that you could fix the inline form issue by defining a custom view to edit the user details rather than using django admin. As for the different profile types - I'd suggest just defining a profile type field in the model and then hiding/showing different fields/properties depending on its value
Related
I am building a simple social media app. AND i am trying to build a feature of adding users into post using ManyToManyField.
I am trying to access profile friends in Post's model instance "add_user" for tagging user.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,default='',unique=True)
full_name = models.CharField(max_length=100,default='')
friends = models.ManyToManyField("Profile",blank=True)
class Post(models.Model):
post_owner = models.ForeignKey(User,default='',null=True,on_delete = models.CASCADE)
post_title = models.CharField(max_length=500,default='')
add_user = models.ManyToManyField(User.profile.friends.all())
I am new in django and I have no idea how can i access user's friend in Post's model instance.
Any help would be much Appreciated.
Thank You in Advance.
You can't give a queryset as an argument for a ManyToManyField, just a class name.
add_users = models.ManyToManyField(User.profile.friends.all()) # you can't do this.
add_users = models.ManyToManyField(User) # Do this.
You shouldn't place you logic in your model's definition.
Do that in your views.
EDIT:
I suggest you use a ModelChoiceField and do the filtering logic there:
class AddFriendForm(forms.Form):
def __init__(self, *args, **kwargs):
try:
user = kwargs.pop('user')
except:
user = None
super(AddFriendForm, self).__init__(*args, **kwargs)
self.fields['friend'].queryset = user.profile.friends.all()
friend = forms.ModelChoiceField(queryset=User.objects.none())
And then in your view you initialize it like this:
def your_view(request):
form = AddFriendForm(user=request.user)
You cannot do
add_users = models.ManyToManyField(User.profile.friends.all())`
Because the models is evaluated once, and need to be non mutable values
You need to do :
add_user = models.ManyToManyField("Profile",blank=True)
And dont forget to add on your ManyToManyField:
related_name="XXX", null=True
I am building a website where users can upload files and can attach uploads to projects that they created beforehand. The upload is done with a django form where the user can specify the title, comments, etc... There is also a dropdown list where the user can choose from the existing projects that he created (list of projects is dependent on user)
As of now the dropdown only shows the (autogenerated) project id which is the pk of the model Project.
I want the dropdown to show the names of the projects and not the project ID which is not very meaningful for the user.
I have already tried
to_field_name='name'
but that didn't work
I have also tried
Project.objects.filter(user=user).values_list('name')
or
Project.objects.filter(user=user).values('name')
the last two options show the project name in {'projectname} but when I select them and submit the form the error "Select a valid choice. That choice is not one of the available choices."
This is my code:
models.py
class Upload(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
upload_date = models.DateTimeField(default=timezone.now)
comments = models.CharField(max_length=10000, null=True)
title = models.CharField(max_length=10000, null=True)
project = models.CharField(max_length=99, default='--None--')
forms.py
class UploadForm(ModelForm):
project = ModelChoiceField(label='Select Project', queryset=Project.objects.all(), to_field_name='name',
empty_label='--Select Project--')
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(UploadForm, self).__init__(*args, **kwargs)
if user is not None:
self.fields['project'].queryset = Project.objects.filter(user=user)
class Meta:
model = Upload
fields = ['title', 'project', 'upload_date', 'comments']
According to docs
The str() method of the model will be called to generate string representations of the objects for use in the field’s choices. To provide customized representations, subclass ModelChoiceField and override label_from_instance. This method will receive a model object and should return a string suitable for representing it.
https://docs.djangoproject.com/en/2.2/ref/forms/fields/#modelchoicefield
so you should define __str__() method for Project model e.g.
def __str__(self):
return self.name
I have made Custom User model in my Django project. Here it is:
class CustomUser(User):
avatar = models.ImageField(upload_to='avatars')
about_myself = models.TextField(max_length=300)
USERNAME_FIELD = 'username'
def __str__(self):
return self.username
def is_author(self):
return 'blog.change_post' and 'blog.add_post' in self.get_all_permissions()
And after it, I changed all Foreign Keys of user to new CustomUser model. It works OK. But I make one new migration and django cause error, when I want to migrate it:
ValueError: Lookup failed for model referenced by field blog.Comment.author: main.CustomUser
My blog.Comment model:
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(CustomUser)
date_create = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post)
What should I do?
Thanks!
Judging from the code you posted, you might be might be better served by extending the user model rather than replacing it. This pattern is usually called a profile model and works via a one-to-one relationship with User.
Profiles provides application specific fields and behaviors, while allowing User to go about it's usual business unchanged. It doesn't require you to muck around with rewriting auth or even necessarily change your foreign keys.
Here's an example of your code written as a profile:
class Profile(models.Model):
# Link to user :
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='avatars')
about_myself = models.TextField(max_length=300)
def __str__(self):
return self.user.username
def is_author(self):
return 'blog.change_post' and 'blog.add_post' in self.user.get_all_permissions()
Comment model:
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL)
date_create = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post)
# How to access the profile:
def check_author(self):
self.author.profile.is_author()
You'll also want to add a signal to create a new profile when a user is registered:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_new_user(sender, created, instance, **kwargs):
if created:
profile = Profile(user=instance)
profile.save()
Django docs on extending users.
If a profile approach doesn't work for you, try inheriting from AbstractUser or AbstractBaseUser instead of User. The abstract models provide the same basic functionality as User and are the preferred technique for recent Django versions.
There are a handful of additional steps however, check out the docs on creating custom users for a run down.
I am writing an application to help employees track projects the are working on.
Part of the form should allow logged in employees to click a drop down and see all projects they are working on. This is the part I am struggling with; specifically getting ONLY the logged in user's projects populated in a drop down. Any help or insight is much appreciated. Thanks……
models.py
class Photo(models.Model):
image = models.ImageField(upload_to='uploads/images/photo')
title = models.CharField(max_length=50)
def __unicode__(self):
return self.title
class Employee(models.Model):
user = models.ForeignKey(User, unique=True)
photo = models.ImageField(upload_to='uploads/images')
department = models.ForeignKey(Department, null=True)
phone = PhoneNumberField("Phone")
def __unicode__(self):
return self.user.get_full_name()
class Projects(models.Model):
name = models.CharField(max_length=40)
student = models.ForeignKey(Student)
photos = models.ManyToManyField(Photo, blank=True, null=True)
forms.py
class ProjectsForm(forms.ModelForm):
employee = get_object_or_404(Employee, user=user)
employee_projects = employee.projects_set.all()
name = forms.ModelChoiceField(queryset=employee_projects,
empty_label="(Select a Project)", required=True)
class Meta:
model = Projects
You need to put first two lines from ProjectsForm class definition to its initialization method and change them a bit.
class ProjectsForm(forms.ModelForm):
name = forms.ModelChoiceField(queryset=Employee.objects.all(),
empty_label="(Select a Project)", required=True)
class Meta:
model = Projects
def __init__(self, user, *args, **kwargs):
super(self, ProjectsForm).init(*args, **kwargs)
employee = get_object_or_404(Employee, user=user)
self.fields['name'].queryset = employee.projects_set.all()
Now, some explanation. Hope someone will find it useful.
In your original ProjectsForm definition, you're trying to get employee's projects when your class is defined. But this happens once your forms.py file is compiled, which takes place rarely (when you change code, for example). Also, you of course have no data that is necessary to filter projects, i.e., no user object, at that stage.
Instead, you need to do this each time the class is initialized. Initialization in Python takes place in special __init__() method. For example, when you're doing something like
form = ProjectsForm(data=request.POST)
in your view, what happens is that ProjectsForm.__init__(data=request.POST) is called to initialize ProjectsForm into an instance.
So, in our new definition, we're requiring a new argument (user) to be passed to the form when it's instantiated. So you can do something like this in your view:
form = ProjectsForm(request.user, data=request.POST)
In new initialization method, first we're calling the initialization method of parent class (which does some internal django things and should be called anyway), then use argument user to get related employee, and then assign a queryset with that employee's projects to the name field.
Sorry if I'm being too verbose.
See also:
Django, passing information when modifying queryset for ModelForm
django - dynamic select fields in forms
Why not have a many to many field in the Employee model that points to all the projects the employee can work on?
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ManyToManyField
I think that would be the best way to do it.
I attached a UserProfile class to User this way:
class UserProfile(models.Model):
url = models.URLField()
home_address = models.TextField()
user = models.ForeignKey(User, unique=True)
I have also implemented auto-creating of UserProfile if needed this way:
def user_post_save(sender, instance, signal, *args, **kwargs):
profile, new = UserProfile.objects.get_or_create(user=instance)
models.signals.post_save.connect(user_post_save, sender=User)
It works fine but I need a small feature - when I go to User Profiles in admin console, I see a list of UserProfiles for existing users. Their titles are shown as UserProfile object. I think it would be nice if I could set titles to corresponding user names, for example, john, kenny etc.
How can I do that?
Define a __unicode__ method for the UserProfile class:
def __unicode__(self):
return u"Profile for %s" % self.user.get_full_name()