I want to design a model for the currrent user to see all the people he referred. I create another model to keep track of all the referral relation between users, such as 'stephen -> jack', 'stephen -> mike'
class CustomUser(AbstractBaseUser):
// ....... a lot more fields
referred_who = models.ForeignKey('referral', blank=True, null=True, on_delete = models.CASCADE)
class Referral(models.Model):
referrer = models.ForeignKey(CustomUser, on_delete = models.CASCADE)
referred = models.ForeignKey(CustomUser, on_delete = models.CASCADE, related_name='referred')
class Meta:
unique_together = (("referrer", "referred"),)
def __str__(self):
return self.referrer.username + ' -> ' + self.referred.username
The question I am having right now is that one user can refer multiple users, but the field 'referred_who' I use to keep track of all the referral relations can only keep one of them. In my back-end admin it shows:
Referred who: stephen1 -> stephen2 (with a dropdown with all the relations but I can only choose one of them)
What I want is somethig like:
Referred who: stephen1 -> stephen2 stephen1 -> stephen3 stephen1 -> stephen4
And I can access this 'Referred_who' field to get all the relations this current user has.
Is there a way to add to the foreign key field instead of just choosing one of them? Can someone show me an example to do it? Many thanks.
You don't need a Referral class. Your referred_who field can "point" to the CustomUser. I renamed it to referred_by and added related_name='referees'. This would mean that if user A referred user B and user B referred user C and D, then B.referred_by == A and B.referees == (C, D):
class CustomUser(AbstractBaseUser):
referred_by = models.ForeignKey('self', blank=True, null=True, on_delete = models.CASCADE, related_name='referees')
Then, you could create a custom form to display in the admin, something like:
from django.contrib import admin
from django import forms
from django.db import models
class CustomUserForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = '__all__'
referees = forms.ModelMultipleChoiceField(queryset=CustomUser.objects.all())
def __init__(self, *args, **kwargs):
super(CustomUserForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['referees'].initial = self.instance.referees.all()
class CustomUserAdmin(admin.ModelAdmin):
form = CustomUserForm
admin.site.register(CustomUser, CustomUserAdmin)
NOTE: This won't save the chosen users in the referees field yet, you would need to do that by overriding the save method in the form but I do hope this gets you started.
Related
I have these two models:
class User(AbstractUser):
is_teacher = models.BooleanField(default=False, null=False)
class Course(models.Model):
teacher = models.ForeignKey(User, on_delete=models.CASCADE, related_name='teacher_courses')
students = models.ManyToManyField(User, blank=True, related_name='student_courses')
Course model has a ManyToMany field and a ForeignKey to User model. In django's admin page, you are able to see a course's student/teacher. Is there a way to make it as you can have a list of a user's courses in admin page to see/add/remove courses for a user?
You can define a callable on your ModelAdmin class and add it to list_display. To make the courses editable on an user's page use sub classes of InlineModelAdmin.
class TeacherCourseInlineAdmin(admin.TabularInline):
model = Course
fk_name = "teacher"
class StudentCourseInlineAdmin(admin.TabularInline):
model = Course
fk_name = "student"
class UserAdmin(admin.ModelAdmin):
list_display = ("username", "teacher_courses")
inlines = [TeacherCourseInlineAdmin, StudentCourseInlineAdmin]
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).prefetch_related("teacher_courses")
#admin.display(description='Courses')
def teacher_courses(self, user):
return [c.name for c in user.teacher_courses.all()]
Note that it makes sense to override ModelAdmin.get_queryset() to add a call to prefetch_related() so that Django fetches all related courses in one extra query instead of performing one additional query for every user object.
I'm working on a project on how to create Two users : buyers/Sellers for Web using Django as Backend.
I've started the app "users"
I've read the Django Documentation about CustomUserModel
But Honestly don't know where to start from.
Any Suggestions?
In my opinion, a Buyer might be a seller and a seller might be a Buyer.
There are some suggestions:
Create your own application named users (This will help you full control User object in future).
Set your AUTH_USER_MODEL settings to users.User: AUTH_USER_MODEL = 'users.User' As you defined.
Define model Seller - OneToOne -> User: This contains seller's properties, Just create when access via user.seller
Define model Buyer - OneToOne -> User: This contains buyer's properties, just create when access via user.buyer
class User(AbstractUser):
# Your user's properties
# Your user's method
#property
def buyer(self):
try:
return Buyer.objects.get(user=self)
except Buyer.DoesNotExist:
default_value_of_buyer = {}
# Or define default value at model fields
return Buyer.objects.create(user=self, **default_value_of_buyer)
#property
def seller(self):
try:
return Seller.objects.get(user=self)
except Seller.DoesNotExist:
default_value_of_seller = {}
# Or define default value at model fields
return Seller.objects.create(user=self, **default_value_of_seller)
class Buyer(models.Model):
"""
You can add this to admin page to make some actions with Buyer
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE, related_name='buyer_profile')
# Other properties of Buyer
def __str__(self):
return "%s's Buyer profile" % self.user
class Seller(models.Model):
"""
You can add this to admin page to make some actions with Seller
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE, related_name='seller_profile')
# Other properties of Seller
def __str__(self):
return "%s's Seller profile" % self.user
It is not that hard buddy look:
you create a model User
class User(models.Model):
.........
types = (('b','buyer'),('s','seller'))
user_type = models.CharField(max_length=7, choices=types)
so every field has this argument called choices, it is a tuple that has tuples in it, every sub tuple is a choice and every choice has two elements the first one is what appears in the back end and the second element is what appears in the user interface, so 'b' is what appears in the back end and 'buyer' is what the user see in the form in the web site. Tell if that didn't work for you
class Account(models.Model):
types = (
('B', 'BUYER' ),('S', 'SELLER')
)
name = models.Charfield(max_length=15)
username = models.CharField(max_length=15)
email = models.EmailField()
password = models.CharField(max_length=16)
user_type = models.CharField(max_length=12, choices=types)
TRY THIS AND THEN GO TO YOUR ADMIN PAGE AND YOU WILL UNDERSTAND EVERYTHING
what I want to achieve is user will submit 3 inputs in the form 1) name 2) dropdown to select technician, 3) multiselect dropdown to select multiple products. Once the user submit the details
it will generate one lead in database with value like name,foreignkey of selected technician and id of selected products in different table. I don't know how to achieve this below I have mentioned my approch to achieve what I want. Please let me know if the models need any changes and how I can write a view for the same.
models.py
class product(models.Model):
name = models.CharField(max_length=20)
class technician(models.Model):
name = models.CharField(max_length=20)
class lead(models.Model):
name = models.CharField(max_length=20)
technician = models.ForeignKey(technician,on_delete=models.SET_NULL,null=True) #only single selection
products = models.ManyToManyField(product) #user can select multiple product in dropdown
form.py
class leadForm(form.ModelForm):
products = forms.MultipleChoiceField(queryset=Product.objects.all())
technician = forms.CharField(max_length=30,choices=[(i.id,i.name) for i in Technician.objects.all().values('id','name')
class Meta:
model = lead
fields = ('name','technician')
You should use a ModelMultipleChoiceField [Django-doc] here. The But in fact you do not need to implement the models yourself. You can simply let the Django logic do the work for you.
In order to give a textual representation at the HTML end, you can override the __str__ functions of the models:
class Product(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Technician(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Lead(models.Model):
name = models.CharField(max_length=20)
technician = models.ForeignKey(Technician, on_delete=models.SET_NULL, null=True)
products = models.ManyToManyField(Product)
Then we can simply define our form with:
class LeadForm(form.ModelForm):
class Meta:
model = Lead
fields = '__all__'
Note: usually classes are written in PamelCase and thus start with an Uppercase.
You can here use a class-based CreateView [Django-doc] for example:
from django.views.generic.edit import CreateView
from app.models import Lead
from app.forms import LeafForm
class LeadCreateView(CreateView):
model = Lead
form_class = LeadForm
template_name = 'create_lead.html'
I have a model with a forgein key to itself. For example:
class Folder(models.Model):
name = models.CharField()
parent_folder = models.ForeignKey('self', null=True, blank=True, default=None, on_delete=models.CASCADE)
For my purposes, I never want parent_folder to refer to itself, but the default admin interface for this model does allow the user to choose its own instance. How can I stop that from happening?
Edit: If you're trying to do a hierarchical tree layout, like I was, another thing you need to watch out for is circular parent relationships. (For example, A's parent is B, B's parent is C, and C's parent is A.) Avoiding that is not part of this question, but I thought I would mention it as a tip.
I would personally do it at the model level, so if you reuse the model in another form, you would get an error as well:
class Folder(models.Model):
name = models.CharField()
parent_folder = models.ForeignKey('self', null=True, blank=True, default=None, on_delete=models.CASCADE)
def clean(self):
if self.parent_folder == self:
raise ValidationError("A folder can't be its own parent")
If you use this model in a form, use a queryset so the model itself doesn't appear:
class FolderForm(forms.ModelForm):
class Meta:
model = Folder
fields = ('name','parent_folder')
def __init__(self, *args, **kwargs):
super(FolderForm, self).__init__(*args, **kwargs)
if hasattr(self, 'instance') and hasattr(self.instance, 'id'):
self.fields['parent_folder'].queryset = Folder.objects.exclude(id=self.instance.id)
To make sure the user does not select the same instance when filling in the foreign key field, implement a clean_FIELDNAME method in the admin form that rejects that bad value.
In this example, the model is Folder and the foreign key is parent_folder:
from django import forms
from django.contrib import admin
from .models import Folder
class FolderAdminForm(forms.ModelForm):
def clean_parent_folder(self):
if self.cleaned_data["parent_folder"] is None:
return None
if self.cleaned_data["parent_folder"].id == self.instance.id:
raise forms.ValidationError("Invalid parent folder, cannot be itself", code="invalid_parent_folder")
return self.cleaned_data["parent_folder"]
class FolderAdmin(admin.ModelAdmin):
form = FolderAdminForm
admin.site.register(Folder, FolderAdmin)
Edit: Combine my answer with raphv's answer for maximum effectiveness.
I've implemented a circular OneToMany relationship at a Django model and tried to use the limit_choices_to option at this very same class.
I can syncdb without any error or warning but the limit is not being respected.
Using shell I'm able to save and at admin I receive the error message:
"Join on field 'type' not permitted.
Did you misspell 'neq' for the lookup
type?"
class AdministrativeArea(models.Model):
type = models.CharField(max_length=1, choices=choices.ADMIN_AREA_TYPES)
name = models.CharField(max_length=60, unique=True)
parent = models.ForeignKey('AdministrativeArea',
null=True,
blank=True,
limit_choices_to = Q(type__neq='p') & Q(type__neq=type)
)
The basic idea for the limit_choices_to option is to guarantee that any type "p" cannot be parent ofr any other AdministrativeArea AND the parent cannot be of the same type as the current AdministrativeArea type.
I'm pretty new to Django ... what am I missing?
Thanks
You can create a model form that adjusts specific field's queryset dynamically when working with existing model instance.
### in my_app/models.py ###
class AdministrativeArea(models.Model):
type = models.CharField(max_length=1, choices=choices.ADMIN_AREA_TYPES)
name = models.CharField(max_length=60, unique=True)
parent = models.ForeignKey('AdministrativeArea',
null=True,
blank=True,
limit_choices_to = Q(type__neq='p')
)
### in my_app/admin.py ###
from django.contrib import admin
import django.forms as forms
from my_app.models import AdministrativeArea
class class AdministrativeAreaAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AdministrativeAreaAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
if instance is not None:
parentField = self.fields['parent']
parentField.queryset = parentField.queryset.filter(type__neq=instance.type)
class Meta:
model = AdministrativeArea
class AdministrativeAreaAdmin(admin.ModelAdmin):
form = AdministrativeAreaAdminForm
Such form could be used also outside admin site if you need the filtering there.