django : How to declare a foreign key of another foreignkey - django

I have a model with:
class Material(models.Model):
name = models.CharField(_('name'), max_length=50)
description = models.TextField(_('description'), blank=True)
user = models.ForeignKey(User, default=None, blank= True, null = True)
class Essai_Temperature(models.Model):
name = models.ForeignKey(Material, verbose_name=_('name'))
nature_unit = models.ForeignKey(Property, verbose_name=_('category'))
user= models.ForeignKey(Material, related_name="user_set", default='0')
admin:
class Essai_TemperatureAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(Essai_TemperatureAdmin, self).queryset(request)
current_user = request.user
if current_user.is_superuser:
return qs
else:
return qs.filter(user=current_user)
How to return qs.filter(user=current_user)?
in Essai_Temperature the field user don't work
How can I have access to the field user in Material?

You're thinking about this in completely the wrong way. The foreign key from Essai is to Material, not to User, so you should call it material (and drop the related_name, which is wrong and confusing).
Now you can follow the relationship:
qs.filter(material__user=current_user)

Related

Using inline formsets with manytomany relationship

I have AbstractUser and Relationship models. The user model has a manytomany field through relationship model
class User(AbstractUser):
username = models.CharField(
verbose_name="username", max_length=256, unique=True)
first_name = models.CharField(verbose_name="first name", max_length=30)
last_name = models.CharField(verbose_name="last name", max_length=30)
slug = models.SlugField(max_length=255, unique=True, blank=True)
date_joined = models.DateTimeField(
verbose_name="joined date", auto_now_add=True)
friends = models.ManyToManyField('self', through='Relationship', symmetrical=False,
related_name='following')
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
def __str__(self):
return str(self.username)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.username)
super().save(*args, **kwargs)
class Relationship(models.Model):
from_person = models.ForeignKey(
User, related_name='from_people', on_delete=models.CASCADE)
to_person = models.ForeignKey(
User, related_name='to_people', on_delete=models.CASCADE)
def __str__(self):
return f"from {self.from_person} to {self.to_person}"
How to create an inline formset with fields=['from_person' and 'to_person'] to assign users to each others
That is what I tried to do but I couldn't have both fields because of fk_name
Any help please!
def staff_profile_view(request):
user = User.objects.get_by_natural_key(request.user)
AssignFriendsFormSet = inlineformset_factory(
User, Relationship, fk_name='from_person', fields=('to_person', 'from_person',))
formset = AssignFriendsFormSet(
queryset=Relationship.objects.none(), instance=user)
if request.method == "POST":
formset = AssignFriendsFormSet(request.POST, instance=user)
if formset.is_valid():
formset.save()
return render(request, 'staff_profile.html',{"formset": formset})
An inline formset won't let you change the 'line' between models, so you can't include 'from_person' as a changeable field. In this case it looks like you're trying to get a list of people following the user/requester (the 'from_person') with the possibility of changing them so they don't follow the user anymore.
Inline formsets are just an abstraction, though - a shorthand for a particular type of queryset. You can achieve exactly the same effect through a normal formset, something like
AssignFriendsFormSet = modelformset_factory(Relationship, fields=('from_person', 'to_person'))
queryset = Relationship.objects.filter(from_person=user)
formset = AssignFriendsFormSet (
queryset=queryset,
)
The many-to-many field is really just a record in the through table, so you can update it there.

how to Combine UserProfile and the User model that are connected through OneToOne relation into a single endpoint?

I have a custom user class and a profile class. Profile class has a OneToOne relation with the custom User. the Serializer is having User as Meta model with adding Profile model in a new field profile extended to the fields tuple. but When I try to get the detail view it returns an error saying Profile field is not an attribute of CustomUser.
I would appreciate if you go over the code that I added below and help me through this.
The User model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class Types(models.TextChoices):
DOCTOR = "DOCTOR", "Doctor"
PATIENT = "PATIENT", "Patient"
# what type of user
type = models.CharField(_("Type"), max_length=50, choices=Types.choices, null=True, blank=False)
avatar = models.ImageField(upload_to="avatars/", null=True, blank=True)
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomBaseUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'type'] #email is required by default
def get_full_name(self):
return self.name
def __str__(self):
return self.email
The Profile Model:
class DoctorProfile(models.Model):
"""Model for Doctors profile"""
class DoctorType(models.TextChoices):
"""Doctor will choose profession category from enum"""
PSYCHIATRIST = "PSYCHIATRIST", "Psychiatrist"
PSYCHOLOGIST = "PSYCHOLOGIST", "Psychologist"
DERMATOLOGIST = "DERMATOLOGIST", "Dermatologist"
SEXUAL_HEALTH = "SEXUAL HEALTH", "Sexual health"
GYNECOLOGIST = "GYNECOLOGIST", "Gynecologist"
INTERNAL_MEDICINE = "INTERNAL MEDICINE", "Internal medicine"
DEVELOPMENTAL_THERAPIST = "DEVELOPMENTAL THERAPIST", "Developmental therapist"
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
doctor_type = models.CharField(
_("Profession Type"),
max_length=70,
choices=DoctorType.choices,
null=True,
blank=False)
title = models.IntegerField(_('Title'), default=1, choices=TITLES)
date_of_birth = models.DateField(null=True, blank=False)
gender = models.IntegerField(_('Gender'), default=1, choices=GENDERS)
registration_number = models.IntegerField(_('Registration Number'), null=True, blank=False)
city = models.CharField(_('City'), max_length=255, null=True, blank=True)
country = models.CharField(_('Country'), max_length=255, null=True, blank=True)
def __str__(self):
return f'profile-{self.id}-{self.title} {self.owner.get_full_name()}'
Serializer:
class DoctorProfileFields(serializers.ModelSerializer):
"""To get the fields from the DoctorProfile. it will be used in the DoctorProfileSerializer"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""retrieve, update and delete profile"""
profile = DoctorProfileFields()
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
profile = validated_data.pop('profile', {})
ModelClass.objects.filter(id=instance.id).update(**validated_data)
if profile:
DoctorProfile.objects.filter(owner=instance).update(**profile)
new_instance = ModelClass.objects.get(id = instance.id)
return new_instance
View:
class DoctorProfileAPIView(generics.RetrieveUpdateDestroyAPIView):
"""To get the doctor profile fields and update and delete"""
serializer_class = DoctorProfileSerializer
queryset = User.objects.all()
def get_object(self):
return get_object_or_404(User, id=self.request.user.id, is_active=True)
What I want is a json response in the detail view like below:
{
"name": the name,
"avatar": avatar,
"profile": {
"doctor_type": "PSYCHIATRIST",
"title": 1,
"date_of_birth": 11-11-1990,
"registration_number": 21547,
}
}
Can Anybody guide me through this..? Or is there any other design approach that meets my objective. My objective is to have the user info + profile info combined in a single endpoint as a whole Profile in the frontend from which the user will see/edit profile.
First of all move the foreign key OneToOne in the CustomUser model, add:
owner = models.OneToOneField('DoctorProfile', on_delete=models.CASCADE, related_name='doctor_profile')
and delete from DoctorProfile:
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
Make all migrations, and now you have to set new data in the db.
In the serializers you are using Nested relationships correctly, so add the attribute many set to False:
profile = DoctorProfileFields(many=False)
Edit
If you cant edit the structure of your models, you can work with SerializerMethodField (not tested):
class DoctorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number')
class CustomDoctorProfileSerializer(serializers.Serializer):
name = serializers.SerializerMethodField()
avatar = serializers.SerializerMethodField()
profile = DoctorProfileSerializer(many=False)
def get_name(self, obj)
return obj.doctor_profile.name
def get_avatar(self, obj)
return obj.doctor_profile.avatar

Display misconception in django-tables2

After creating a custom user model in my app, I have a studentProfile that inherits from the user model, which also contains avatar, semester, and dept_name. which works fine. However, when I was trying to display this studentProfile data using django-tables2, all rows keeps showing "-" and the ID been captured is from user model instead of studentProfile.
The weirdiest thing is i can get all the values from user model
correctly even when studentProfile is my table model for
django-tables2
I don't know what I am doing wrongly. Any help is really appreciated
my model definitions are as follow
class DepartmentData(models.Model):
fid = models.ForeignKey(FacultyData, on_delete=models.CASCADE)
dept_name = models.CharField(max_length=50)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.dept_name
class User(AbstractBaseUser):
# add additional fields here
user_id = models.CharField(max_length=15, unique=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
active = models.BooleanField(default=True)# can login
staff = models.BooleanField(default=False) # staff user non superuser
admin = models.BooleanField(default=False) # superuser
USER_TYPE_CHOICES = (
(1, 'student'),
(2, 'lecturer'),
(3, 'bursary'),
(4, 'system'),
(5, 'admin'),
)
user_type = models.PositiveSmallIntegerField(choices=USER_TYPE_CHOICES)
USERNAME_FIELD = 'user_id'
REQUIRED_FIELDS = ['first_name', 'last_name', 'user_type']
objects = UserManager()
def __str__(self):
return self.user_id
def get_full_name(self):
return self.first_name + " " + self.last_name
def get_user_type(self):
return self.user_type
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
return self.staff
#property
def is_admin(self):
return self.admin
#property
def is_active(self):
return self.active
class StudentProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
semester = models.ForeignKey(SemesterData, on_delete=models.SET_NULL, null=True)
dept_name = models.ForeignKey(DepartmentData, on_delete=models.SET_NULL, null=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
def __str__(self):
return self.user.first_name
class SemesterData(models.Model):
sid = models.ForeignKey(SessionData, on_delete=models.CASCADE)
semester_name = models.CharField(max_length=50)
def __str__(self):
return self.semester_name
def current(self):
if SettingsData.objects.all().count():
st = SettingsData.objects.get(id=1)
if self.id == st.current_id:
return "Current Session-Semester"
else:
return format_html('{}', reverse('system:current_session_semester', args=[self.id]),
'Set Current')
else:
return format_html('{}', reverse('system:current_session_semester', args=[self.id]),
'Set Current')
here is my table.py
class StudentTable(tables.Table):
user_id = tables.Column(attrs = {'th': {'class': 'danger'}})
first_name = tables.Column(attrs = {'th': {'class': 'danger'}})
last_name = tables.Column(attrs = {'th': {'class': 'danger'}})
avatar = tables.Column(accessor ="user", verbose_name = "ass" )
active = tables.Column(attrs = {'th': {'class': 'danger'}})
last_login = tables.Column(attrs = {'th': {'class': 'danger'}})
edit_Action = tables.LinkColumn('system:semester_edit', text='Edit', args=[A('pk')],attrs={'a':{'class':'btn btn-info btn-sm'}, 'td':{'align': 'center'}, 'th': {'class': 'danger'}}, orderable=False)
class Meta:
model = StudentProfile
attrs = {'class':'table table-hover table-bordered table-responsive'}
sequence = ('user_id', 'first_name', 'last_name', 'avatar')
exclude = {'id', 'user', 'password', 'staff', 'admin'}
empty_text = _("There are no students yet")
template_name = 'django_tables2/bootstrap4.html'
I would love to get the department_name, semester_name as well as fields in the studentProfile which is serving as my table model
You are seeing empty values for all fields with your current configuration because you're trying to access fields user_id, first_name and last_name which are not fields of the StudentProfile model, but rather fields of the User model (to which StudentProfile is related by user field).
That being said, you should access those fields via the user relation, something like this:
class StudentTable(tables.Table):
user_id = tables.Column(accessor='user.user_id', ...)
first_name = tables.Column(accessor='user.first_name', ...)
last_name = tables.Column(accessor='user.last_name', ...)
...
As far as the DepartmentData and SemesterData relations go, I'm not sure why aren't they displayed by default, since they are fields of the StudentProfile model, and they aren't excluded via the exclude property on the Meta. You can maybe try to explicitly list them in the fields property and see if that helps.

Use value of related field when saving/POST django-rest-framework

Im trying to find a method for posting a string value and saving it to a foreign key field instead of using the pk.
My models:
class CustomUser(models.Model):
username = models.CharField(max_length=500, unique=True)
def __str__(self):
return self.username
class Order(models.Model):
ordernumber = models.UUIDField(primary_key=False, default=uuid.uuid4, editable=False)
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True)
amount = models.FloatField(null=True, blank=True)
def __str__(self):
return "{0}".format(self.ordernumber)
And my serializer:
class OrderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Order
fields = ('id','username', 'ordernumber', 'amount')
read_only_fields = ('id')
When using GET, everything works perfectly, but I can't seem to fix the POST method.
I tried to override the create method like this:
def create(self, validated_data):
username = validated_data.pop('username')
order = Order.objects.create(**validated_data)
order.user = CustomUser.objects.get(username=username)
order.save()
return order
But I get a KeyError on the username = validated_data.pop('username') line: Exception Value:'username'
When you use source with nested fields, data will be accessible as validated_data['parent_field']['child_field']. Try this:
def create(self, validated_data):
user_data = validated_data.pop('user')
username = user_data.pop('username')
order = Order.objects.create(**validated_data)
order.user = CustomUser.objects.get(username=username)
order.save()
return order

how to pass the an authenticated user to forms.py in django

I have a django app with which every registered user can create categories. For the authentication I am using django-all-auth. My models.py looks like this:
class Profile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE)
create_date = models.DateTimeField('date added', auto_now_add=True)
modify_date = models.DateTimeField('date modified', default=timezone.now)
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(Profile, on_delete=models.CASCADE)
name = models.CharField(max_length=200, unique=True)
create_date = models.DateTimeField('date added', auto_now_add=True)
modify_date = models.DateTimeField('date modified', default=timezone.now)
On the index page the user can see the created categories and create new ones.
The views.py:
def CategoryView(request):
user = 0
if request.user.is_authenticated():
user = request.user
form = CategoryNameForm()
form.user = user
context = {
'categories': Category.objects.all(),
'form': form,
'user':user,
}
if request.method == 'POST':
form = CategoryNameForm(request.POST)
form.user = user
if form.is_valid():
form.save()
return render(request, 'myapp/index.html',context)
forms.py:
class CategoryNameForm(forms.ModelForm):
class Meta:
model = Category
fields = ('name',)
The authentication works. So I was thinking to just put pass the user field into the form :
class CategoryNameForm(forms.ModelForm):
class Meta:
model = Category
fields = ('name','user',)
hide it and then, just select it via JS since the user is in the context. I was just wondering if there is an easier way. This form.user = user for some reason didn't work, I get a NOT NULL constraint failure
There are couple of ways but here is one:
class CategoryNameForm(forms.ModelForm):
class Meta:
model = Category
fields = ('name',) # take out user you don't need it here
def save(self, **kwargs):
user = kwargs.pop('user')
instance = super(CategoryNameForm, self).save(**kwargs)
instance.user = user
instance.save()
return instance
Then in view:
if form.is_valid():
form.save(user=request.user, commit=False)
Make sure your CategoryView is only accessible by authenticated user. Otherwise you will still get NOT NULL constraint failure for user.