I'm working on a project using Python(3.7) and Django(2.2) in which I have implemented my models for multiple user types with custom user model as the base model. Everything working fine except the admin side, I have register these modles to admin but when I try to add an object from admin interface it's giving an error.
Here's what I have tried so far:
From models.py:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=254, unique=True)
title = models.CharField(max_length=255, blank=False)
user_type = models.CharField(max_length=255, choices=USER_TYPE, blank=False)
gender = models.CharField(max_length=255, choices=CHOICES, blank=False)
contenst = models.CharField(max_length=255, blank=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['password']
objects = UserManager()
def get_absolute_url(self):
return "/users/%i/" % (self.pk)
class PersonalBelow18(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
dob = models.DateField(blank=False)
customer_id = models.BigIntegerField(blank=False)
collection_use_personal_data = models.BooleanField(blank=False)
reference_identities = models.ForeignKey(Identities, blank=False, on_delete=models.CASCADE, related_name='refs')
def __str__(self):
return self.user.email+'\'s account with ' + str(self.customer_id)
class PersonalAbove18(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dob = models.DateField(blank=False)
customer_id = models.BigIntegerField(blank=False)
contact_email = models.EmailField(blank=False)
reference_identities = models.ForeignKey(Identities, blank=False, on_delete=models.CASCADE)
contact_no = PhoneNumberField(blank=True, help_text='Phone number must be entered in the'
'format: \'+999999999\'. Up to 15 digits allowed.')
collection_use_personal_data = models.BooleanField(blank=False)
def __str__(self):
return self.user.email+'\'s account with ' + str(self.customer_id)
class Parent(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
contact_email = models.EmailField(blank=False)
customer_id = models.BigIntegerField(blank=True)
contact_no = PhoneNumberField(blank=True, help_text='Phone number must be entered in the'
'format: \'+999999999\'. Up to 15 digits allowed.')
collection_use_personal_data = models.BooleanField(blank=False)
class GroupContactPerson(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
contact_email = models.EmailField(blank=False)
customer_id = models.BigIntegerField(blank=False)
contact_no = PhoneNumberField(blank=True, help_text='Phone number must be entered in the'
'format: \'+999999999\'. Up to 15 digits allowed.')
department = models.CharField(max_length=255, blank=False)
address = models.TextField(max_length=255, blank=False)
and here's how I register these models to admin:
From admin.py:
class UserAdmin(BaseUserAdmin):
fieldsets = (
(None, {'fields': ('email', 'password', 'title', 'user_type',
'gender', 'contenst', 'last_login')}),
('Permissions', {'fields': (
'is_active',
'is_staff',
'is_superuser',
'groups',
'user_permissions',
)}),
)
add_fieldsets = (
(
None,
{
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')
}
),
)
list_display = ('email', 'title', 'is_staff', 'last_login')
list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ('groups', 'user_permissions',)
admin.site.register(User, UserAdmin)
admin.site.register(PersonalBelow18)
admin.site.register(PersonalAbove18)
admin.site.register(Parent)
admin.site.register(GroupContactPerson)
The Parent and GroupContactPerson models are working well on admin side but the when I try to add an object for PersonalBelow18 & PersonalAbove18 models, it gives the following error as:
TypeError: str returned non-string (type int)
Here's how I debug this problem in these models:
I start removing all fields one-by-one
Remove a field from model & form and perform migrations
Then test the admin
Then I found that when I removed the reference_identities field it start working, so I get that this model was returning an integer, so I fixed that model and it fix the issue.
In short, it's a good approach to find a path to the actual place of problem by removing fields one-by-one and test the admin.
Related
I have a model that connects a school model and a user model through a foreign key, and on creating if a student is being created I want the user foreign key to be set as the instance and also a school to be chosen by its id however i can't solve this error
TypeError at /api/users/student_register/
django.db.models.manager.BaseManager._get_queryset_methods.<locals>.create_method.<locals>.manager_method() argument after ** must be a mapping, not str
class User(AbstractBaseUser, PermissionsMixin):
....
email = models.EmailField(
is_superuser = models.BooleanField(default=False)
last_login = models.DateTimeField(
_("last login"), auto_now=True, auto_now_add=False)
class School(models.Model):
PROVINCE = (
...
)
SCHOOLTYPE = (
....
)
name = models.CharField(_("Name"), max_length=150, blank=False, null=True)
abbr = models.CharField(
_("Abbrivation"), max_length=10, blank=False, null=True)
zone = models.CharField(
_("Zone"), max_length=50, choices=PROVINCE)
Schooltype = models.CharField(
_("School type"), max_length=50, choices=SCHOOLTYPE)
schoolemail = models.EmailField(
_("School email"), max_length=254, blank=False, null=True) editable=False)
def __str__(self):
return self.name
this applied model is what is connecting the user and the student model together and it is what i am using though the nested serializer to create the users
class Applied(models.Model):
class TYPES(models.TextChoices):
STUDENT = "STUDENT", "Student"
WORKER = "WORKER", "Worker"
type = models.CharField(_("type"), choices=TYPES.choices,
max_length=150, blank=False, null=True)
user = models.ForeignKey(User, verbose_name=_(
"useris"), related_name='useris', on_delete=models.PROTECT, blank=True, null=True)
school = models.ForeignKey(School, verbose_name=_(
'school'), related_name="applied", on_delete=models.CASCADE, blank=True, null=True)
serializers.py
class StudentSchoolSerializer(serializers.ModelSerializer):
user = serializers.CharField()
school = serializers.CharField()
type = serializers.HiddenField(
default=Applied.TYPES.STUDENT
)
class Meta:
model = Applied
fields = ['school', 'type', 'user']
# register a student
class RegisterSerializerStudent(serializers.ModelSerializer):
applied = StudentSchoolSerializer(required=True)
password = serializers.CharField(
write_only=True, required=True, validators=[validate_password])
firstname = serializers.CharField(required=True)
middlename = serializers.CharField(required=True)
lastname = serializers.CharField(required=True)
class Meta:
model = User
fields = ('firstname', 'middlename', 'sex', 'grade', 'image', 'room', 'type', 'lastname', 'age', 'password',
'phone', 'applied')
extra_kwargs = {'password': {'write_only': True},
'firstname': {'required': True},
'middelname': {'required': True},
'lastname': {'required': True},
'age': {'min_value': 1, 'max_value': 100}
}
def create(self, validated_data):
def create_new_id_number():
S = 10
not_unique = True
while not_unique:
unique_id = ''.join(random.choices(
string.ascii_uppercase + string.digits, k=S))
if not User.objects.filter(student_id=unique_id):
not_unique = False
return unique_id
applied = validated_data.pop('applied')
user = User.objects.create(
student_id=create_new_id_number(),
firstname=validated_data['firstname'],
middlename=validated_data['middlename'],
lastname=validated_data['lastname'],
age=validated_data['age'],
phone=validated_data['phone'],
image=validated_data['image'],
room=validated_data['room'],
sex=validated_data['sex'],
is_active='True',
type=User.TYPES.STUDENT,
)
user.set_password(validated_data['password'])
user.save()
for applies in applied:
Applied.objects.create(**applies, user=user)
return user
create method throws the error, creates a User instance but not the Applied instance
applied = validated_data.pop('applied') contained the value of "applied" in your serializer. You did not call StudentSchoolSerializer with the many=true option. So when you do
for applies in applied:
Applied.objects.create(**applies, user=user)
applies is actually a str, not a dict. So you can't use **.
If you are sending only one "applied", you can save it directly
Applied.objects.create(**applied, user=user)
I created a user profile model for my system. I created all models and it works perfectly. I have a form, and the form works too. But when I look user create form from admin page, it doesn't look the same.
There are some missing parts like rank, comp_name. How can I fix it?
models.py
class UserProfile(models.Model):
ranks = (
('xxx', 'xxx'),
...
)
comp_name = models.CharField(max_length=200, default="Choose")
user_id = models.UUIDField(default=uuid.uuid4(), editable=False, unique=True)
username = models.CharField(max_length=500)
first_name = models.CharField(max_length=200, default=None)
last_name = models.CharField(max_length=200, default=None)
password = models.CharField(max_length=50)
email = models.EmailField(max_length=254)
rank = models.CharField(max_length=200, choices=ranks)
forms.py
class SignUpForm(UserCreationForm):
comp_name = forms.CharField(label='What is your company name?')
email = forms.CharField(max_length=254)
rank = forms.ChoiceField(label='What is your rank?', choices=UserProfile.ranks)
first_name = forms.CharField(max_length=250)
last_name = forms.CharField(max_length=250)
comp_name = forms.ModelChoiceField(queryset=CompanyProfile.objects.all())
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'comp_name', 'password1', 'password2', 'rank'
admin
admin panel "Change User" screen
In forms.py
class SignUpForm(UserCreationForm):
comp_name = forms.CharField(label='What is your company name?')
email = forms.CharField(max_length=254)
rank = forms.ChoiceField(label='What is your rank?', choices=UserProfile.ranks)
first_name = forms.CharField(max_length=250)
last_name = forms.CharField(max_length=250)
comp_name = forms.ModelChoiceField(queryset=CompanyProfile.objects.all())
class Meta:
model = User --> change to UserProfile
fields = ('username', 'first_name', 'last_name', 'email', 'comp_name', 'password1', 'password2', 'rank'
class MemberAdmin(CustomUserAdmin, admin.ModelAdmin):
redirect_model_name = 'memberaccountproxy'
change_form_template = 'loginas/change_form.html'
list_display = ('username', 'email', 'first_name', 'last_name','country', 'gender',
'created_at', 'profile_pic')
search_fields = ('username', 'first_name', 'last_name', 'email',)
add_form = AccountProfileForm
list_filter = (
'gender', 'incomplete', 'email_verified', 'suspended',
'deleted',
)
# profile1 = None
def get_queryset(self, objects):
return Account.objects.filter(usertype=1)
def country(self, obj):
profile1 = Profile.objects.get(account=obj)
if profile1.country:
return profile1.country
return ''
country.admin_order_field = 'country'
What I am trying to achieve is to make my country column on Member list page sortable.(currently its not)
Problem is that I get the 'obj' from method country as an Account object(as defined in the get_queryset()), & later I use that obj to get another object of type Profile (which has a member variable country). But when I try to register the country method to 'admin_order_field', it can only take member variables of obj (of type Account, which doesn't have country variable in it).
Is there a way to sort the country column based on the field from Profile class instead of Account.
class Profile(models.Model):
account = models.OneToOneField(Account)
country = models.ForeignKey('Country', null=True, blank=True, verbose_name='Country Living In', related_name='+')
# name = models.CharField(max_length=40, blank=True)
status = models.ForeignKey('Status', blank=True, null=True, verbose_name='Marital Status', related_name='+')
btype = models.ForeignKey('Btype', blank=True, null=True, verbose_name='Body type', related_name='+')
bheight = models.IntegerField(choices=HEIGHT, blank=True, null=True, verbose_name='Height')
phystatus = models.ForeignKey('EthnicOrigin', blank=True, null=True, verbose_name='Ethnic Origin', related_name='+')
createdby = models.ForeignKey('CreatedBy', blank=True, null=True, verbose_name='Profile Created By',
related_name='+')
...........................
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
usertype = models.IntegerField(default=1, choices=USER)
username = models.CharField(max_length=40, unique=True)
gender = models.CharField(choices=(('Male', 'Male'), ('Female', 'Female')), max_length=10, blank=True)
phone = models.CharField(max_length=16, blank=True, verbose_name="Mobile No", null=True)
temple = models.ManyToManyField('Account', blank=True, null=True, verbose_name="MatchMaker")
first_name = models.CharField(max_length=40, blank=True, null=True)
last_name = models.CharField(max_length=40, blank=True, null=True)
acc_for = models.ForeignKey('CreatedBy', blank=True, null=True, verbose_name="Profile Created For")
country_code = models.ForeignKey('CountryCode', max_length=40, blank=True, null=True)
............................................
The list_display and admin_order_field option supports double underscore notation to access related models.
Your question isn't clear because you haven't shown your models, but it looks like you can do:
country.admin_order_field = 'profile__country'
However, you can probably replace country with profile__country in your list_display method, and remove your country method entirely.
I am new to Django. I am using Django 1.8 and Python 3.4.
class Card(models.Model):
STATUS_EXPIRED = "EX"
STATUS_ACTIVE = "AC"
STATUS_DEACTIVATED = "DE"
STATUS_CHOICES = (
(STATUS_ACTIVE, "Active"),
(STATUS_EXPIRED, "Expired"),
(STATUS_DEACTIVATED, "Deactivated")
)
id = models.AutoField(primary_key = True)
series = models.CharField(verbose_name="Series", max_length=8, null=False, blank=False)
number = models.CharField(verbose_name="Number", max_length=16, null=False, blank=False)
issue_date = models.DateTimeField(verbose_name="Issue Date", auto_now=True, null=False, blank=False)
expire_date = models.DateTimeField(verbose_name="Expiry Date", auto_now=False, null=False, blank=False)
status = models.CharField(verbose_name="Status", max_length=3, null=False, blank=False, default="AC")
How do I ensure that expire_date is never less than issue_date in the database? How do I enforce this condition in Django-admin interface when creating objects of Card class?
You can do a form validation in admin like this;
from models import Card
from django.contrib import admin
from django import forms
class CardForm(forms.ModelForm):
class Meta:
model = Card
fields = ('series', 'number', 'issue_date', 'expire_date', 'status')
def clean(self):
issue_date = self.cleaned_data.get('issue_date')
expire_date = self.cleaned_data.get('expire_date')
if expire_date < issue_date:
raise forms.ValidationError("Wrong dates")
return self.cleaned_data
class CardAdmin(admin.ModelAdmin):
form = CardForm
list_display = ('series', 'number', 'issue_date', 'expire_date', 'status')
admin.site.register(Card, CardAdmin)
You can use a custom model form ( Model forms ) and validate the data before saving and while registering do something like
class TestAdmin(admin.ModelAdmin):
form = ModelForm
list_display = ('field1', 'field2')
admin.site.register(Lecture, LectureAdmin)
I am creating a view that handles a new Customer registration. My Customer model OneToOne links to Person which again OneToOne links to User where User is AUTH_USER_MODEL. When I send a POST request for customer registration through the register_customer view, person_serialized.is_valid() returns {"user":["This field is required."]} as the user object is not available and passed on the PersonSerializer. I think there should be a better and cleaner to do what I am doing here. Any solutions?
models.py
class User(AbstractBaseUser, PermissionsMixin):
USERNAME_FIELD = 'mobile_number'
# Form validation errors
mobile_number_errors = {'required': 'Mobile number is required',
'invalid': 'Enter a valid 10 digit mobile number' +
'without spaces, + or isd code.'}
_mobile_regex_validator = RegexValidator(regex=r"^\d{10}$",
message="Phone number must be 10 digits without + or spaces.")
mobile_number = models.CharField("Mobile Number", max_length=10,
validators=[_mobile_regex_validator],
blank=False, null=False, unique=True,
error_messages=mobile_number_errors)
is_active = models.BooleanField("Is Active?", default=True)
is_staff = models.BooleanField("Is Staff?", default=False)
created = models.DateTimeField("Account created on", auto_now_add=True, blank=True, null=True)
modified = models.DateTimeField("Last Modified on", auto_now=True, blank=True, null=True)
objects = UserManager()
def __unicode__(self):
return self.mobile_number
def get_full_name(self):
return self.mobile_number
def get_short_name(self):
return self.mobile_number
class Person(models.Model):
user = models.OneToOneField('users.User')
GENDER_CHOICES = (('M', 'Male'),
('F', 'Female'),
('N', 'Not Specified'))
first_name = models.CharField("First Name", max_length=32, blank=False, null=False)
last_name = models.CharField("Last Name", max_length=32, blank=False, null=False)
gender = models.CharField("Gender", max_length=1, choices=GENDER_CHOICES, blank=False, default='N')
class Customer(models.Model):
person = models.OneToOneField('users.Person')
email_errors = {'required': 'Email field is required.',
'invalid': 'Enter a valid email id.'}
email = models.EmailField("Email", blank=False, null=False, unique=True,
error_messages=email_error
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'mobile_number',)
class PersonSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Person
fields = ('id', 'user', 'first_name', 'last_name', 'gender',)
class CustomerSerializer(serializers.ModelSerializer):
person = PersonSerializer()
class Meta:
model = Customer
fields = ('id', 'person', 'email',)
views.py
from django.shortcuts import render
from .serializers import UserSerializer, PersonSerializer, CustomerSerializer
from .models import User, Person, Customer
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
# Create your views here.
#api_view(['POST'])
def register_customer(request):
user_serialized = UserSerializer(data=request.data)
person_serialized = PersonSerializer(data=request.data)
customer_serialized = CustomerSerializer(data=request.data)
if user_serialized.is_valid() and person_serialized.is_valid() and customer_serialized.is_valid():
user = User.objects.create_user(mobile_number=user_serialized.data['mobile_number'],
password=user_serialized.init_data['password'])
person = Person(user=user, first_name=person_serialized.data['first_name'],
last_name=person_serialized.data['last_name'],
gender=person_serialized.data['gender'])
person.save()
customer = Customer(person=person, email=customer_serialized.data['email'])
customer.save()
return Response(request.data, status=status.HTTP_201_CREATED)
else:
return Response(person_serialized._errors, status=status.HTTP_400_BAD_REQUEST)