I am adding a following/follower system and I made it a foreign field in my profile model. The thing is, I want to be able to save multiple information to it like the time when a particular user started following and other future info, so I made the many to many field pass through a model, as shown here: https://docs.djangoproject.com/en/3.0/topics/db/models/#extra-fields-on-many-to-many-relationships
Please, I will like to know what I am missing as I am getting
AttributeError at /api/opeyemi-odedeyi-ikx5yh/follow/
'User' object has no attribute 'following'
I have tried this:
models.py
class Profile(models.Model):
SEX= (
('M', 'Male'),
('F', 'Female'),
)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profiles')
date_of_birth = models.DateField(blank=True, verbose_name="DOB", null=True)
bio = models.TextField(max_length=500, blank=True, null=True)
sex = models.CharField(max_length=1, choices=SEX, blank=True, null=True)
following = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, through='Follow', related_name='followed_by')
updated_on = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.fullname
class Follow(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='user_following')
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='profile')
date_followed = models.DateField(auto_now_add=True)
serializers.py
class ProfileDetailedSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(read_only=True, slug_field='slug')
age = serializers.SerializerMethodField(read_only=True)
following = serializers.SlugRelatedField(read_only=True, slug_field='slug', many=True)
following_count = serializers.SerializerMethodField(read_only=True)
user_has_followed = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Profile
fields = "__all__"
read_only_fields = ('pk', 'user')
def get_age(self, instance):
today = date.today()
dob = instance.date_of_birth
if dob==None:
return None
return today.year - dob.year - ((today.month, today.day) < (dob.month, dob.day))
def get_following_count(self, instance):
return instance.following.count()
def get_user_has_followed(self, instance):
request = self.context.get("request")
return instance.following.filter(pk=request.user.pk).exists()
views.py
class UserFollowAPIView(APIView):
'''
Can follow(post) and unfollow(delete) the user
'''
serializer_class = ProfileDetailedSerializer
permission_classes = [IsAuthenticated]
def delete(self, request, slug):
followUser = get_object_or_404(User, slug=slug)
user = self.request.user
followUser.following.remove(user)
followUser.save()
serializer_context = {"request": request}
serializer = self.serializer_class(followUser, context=serializer_context)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, slug):
followUser = get_object_or_404(User, slug=slug)
user = self.request.user
followUser.following.add(user)
followUser.save()
serializer_context = {"request": request}
serializer = self.serializer_class(followUser, context=serializer_context)
return Response(serializer.data, status=status.HTTP_200_OK)
urls.py
path("<slug:slug>/follow/", UserFollowAPIView.as_view(), name="user-follow"),
Note:
I used slug because I am not using username in my app, so I had a slug field and made it the lookup field instead of the normal pk
The serializer_context is so the user cannot follow when it returns true (i.e they cannot follow twice)
Related
I am working on Hospital Management System and there are 5-6 different types of users like Patient, Doctor, Nurse, Accountant, Receptionist, etc. I've extended the User model using AbstractUser which has common fields for all users like DoB, address, etc.
models.py
class User(AbstractUser):
# user_type = models.CharField(choices=USER_TYPES, default='patient', max_length=20)
date_of_birth = models.DateField(blank=True, null=True, validators=[validate_future_date])
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+9199999999'. Up to 15 digits allowed.")
mobile_num = models.CharField(max_length=15, validators=[phone_regex])
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
date_joining = models.DateTimeField(auto_now_add=True)
photo = models.ImageField(default="default.png", upload_to="patients/%Y/%m/%d", blank=True)
# other fields
def __str__(self):
return f"{self.first_name} {self.last_name}({self.username})"
class Staff(models.Model):
aadhar_number = models.BigIntegerField(verbose_name='Aadhar Number')
empCategory = models.CharField(max_length=20, blank=True, verbose_name='Employee Category')
class Meta:
abstract = True
class Patient(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date_of_discharge = models.DateTimeField(auto_now=True)
allergies = models.TextField(blank=True, null=True)
def __str__(self):
return f"{self.user.first_name} {self.user.last_name}({self.user.username})"
class Doctor(models.Model):
DEPARTMENTS = [('Cardiologist', 'Cardiologist'),
('Dermatologists', 'Dermatologists'),
('Emergency Medicine Specialists', 'Emergency Medicine Specialists'),
('Allergists/Immunologists', 'Allergists/Immunologists'),
('Anesthesiologists', 'Anesthesiologists'),
('Colon and Rectal Surgeons', 'Colon and Rectal Surgeons')
]
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
languages = models.CharField(max_length=20)
speciality = models.CharField(max_length=20)
department = models.CharField(max_length=50, choices=DEPARTMENTS)
# patients = models.ManyToManyField(Patient, related_name='doctors')
def __str__(self):
return f"{self.user.first_name} {self.user.last_name}({self.user.username})"
class Receptionist(Staff):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# patient = models.ManyToManyField(Patient, related_name='receptionists', blank=True)
def __str__(self):
return f"{self.user.first_name}"
class Nurse(Staff):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'Nurses'
class Accountant(Staff):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
forms.py
class UserRegistrationForm(forms.ModelForm):
password = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat Password', widget=forms.PasswordInput)
email = forms.EmailField(label='Email')
date_of_birth = forms.DateField(widget=forms.widgets.DateInput(attrs={'type': 'date'}))
class Meta:
# gives current active model either Custom User or default User model
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email',
'date_of_birth', 'photo', 'mobile_num', 'gender',
'father_name', 'mother_name', 'blood_group', 'marital_status',
'address1', 'address2', 'city', 'zipcode', 'state'
]
exclude = ['groups', 'superuser_status',
'is_staff', 'is_superuser', ]
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError("Passwords don't match")
return cd['password2']
class PatientRegistrationForm(forms.ModelForm):
class Meta:
model = Patient
fields = ['allergies']
exclude = ['user', ]
views.py
class UserRegistrationMixin(TemplateResponseMixin, View):
form_class = UserRegistrationForm
template_name = 'accounts/register.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_form'] = self.form_class
return context
def get(self, request):
user_form = self.form_class
return self.render_to_response({'user_form':user_form})
def post(self, request):
user_form = self.form_class(data=request.POST, files=request.FILES)
if user_form.is_valid():
new_user = user_form.save(commit=False)
password = user_form.cleaned_data['password']
new_user.set_password(password)
new_user.save()
messages.success(request, f'New user "{new_user}" created!')
return self.render_to_response({'user_form':user_form})
class PatientRegistrationView(UserRegistrationMixin, CreateView):
profile_form_class = PatientRegistrationForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['profile_form'] = self.profile_form_class
return context
def get(self, request):
profile_form = self.profile_form_class
return self.render_to_response({'profile_form':profile_form})
def post(self, request):
profile_form = self.profile_form_class(data=request.POST, files=request.FILES)
if profile_form.is_valid():
# Patient.objects.create()
profile = profile_form.save(commit=False)
profile.user = new_user
profile.save()
messages.success(request, f'Profile for user "{new_user}" created!')
return self.render_to_response({'profile_form':profile_form})
I am trying to separate common logic for registering users and creating profiles for each user i.e. UserRegistrationMixin has logic to create a user and PatientRegistrationView will create a patient's profile and its permissions. But when I inherit from UserRegistrationMixin, user_form doesn't show. I don't have much experience in Django's GCBV. Thanks in advance.
The problem is your get method. The get_context_data doesn't called. You can check the get method of View class:
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
You can call context = self.get_context_data() and pass the context to render_to_response.
I am trying to get the most out of my Django Blog project and wanted to know how to show in the admin the activities that a user has made like making comments or giving likes to posts.
I need some hints and guidance on how to add this information in the admin.py
Here is the models.py in Blog App
class Post(models.Model):
title = models.CharField(max_length=100, unique=True)
content = RichTextUploadingField(null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author')
date_posted = models.DateTimeField(default=timezone.now)
slug = models.SlugField(blank=True, null=True, max_length=120)
liked = models.ManyToManyField(User, default=None, blank=True, related_name='liked')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
class Meta:
verbose_name_plural = 'Blog Posts'
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
content = models.TextField(max_length=300)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.post}-{self.user}-Comment No.{self.pk}"
LIKE_CHOICES = (
('Like', 'Like'),
('Unlike', 'Unlike')
)
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
value = models.CharField(choices=LIKE_CHOICES, max_length=8)
created = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.post}-{self.user}-{self.value}"
Here is the models.py in Users App
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
This is what I have tried in the models.py in Users app but didn't work
# Get the no. of posts
def get_posts_no(self):
return self.author.all().count()
# Get the no. of likes
def get_likes_given(self):
likes= set.like_set.all()
total_liked=0
for item in likes:
if item.value=='Like':
total_liked+=1
return total_liked
"... to add this information in the admin.py" → do you mean to say that you would like to show an additional column in the Django admin site with said information? If so, then the answer is:
In your admin.py, extend the PostAdmin class with a custom queryset and add comments_count and likes_count to list_display = (...):
from django.contrib import admin
from django.db.models import Count
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'date_posted', 'comments_count', 'likes_count')
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_comments_count=Count('comment_set', distinct=True),
_likes_count=Count('like_set', distinct=True)
)
return queryset
def comments_count(self, obj):
if hasattr(obj, '_comments_count'):
return obj._comments_count
return None
def likes_count(self, obj):
if hasattr(obj, '_likes_count'):
return obj._likes_count
return None
comments_count.admin_order_field = '_comments_count'
likes_count.admin_order_field = '_likes_count'
admin.site.register(Post, PostAdmin)
I'm working on a Rest API for a web blog, I have made the custom user class and related between custom user class and the post class, but when i try to make a post request and add a post to the database i'm stuck in serializering the post because of the the relation between the user and the post models, all i want to do is just make the author of the post is the current logged in user but i don't actually know how to do this
Custom user model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True, blank=False, null=False)
username = models.CharField(max_length=255, unique=True, blank=False, null=False)
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = CustomUserManager()
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_superuser
Post model:
class Post(models.Model):
title = models.CharField(max_length=255, blank=False, null=False)
content = models.CharField(max_length=10000, blank=False, null=False)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.title
Post serializer:
class PostSerializer(ModelSerializer):
class Meta:
model = Post
# I don't know if this way is right
fields = ('id', 'title', 'content', 'author')
extra_kwargs = {"author": {"read_only": True}}
def create(self, validated_data, request):
post = Post(
title=validated_data['title'],
content=validated_data['content'],
# I don't know what's gonna be assigned to the author
author=
)
post.save()
return post
Post View:
class AddPostView(APIView):
serializer_class = serializers.PostSerializer
def post(self, request):
serializer = serializers.PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
At first, it doesn't seem to me like you need to specify your custom create method in PostSerializer. And author field can't be read_only otherwise it will be ignored and won't be saved. So it could just look like this:
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'author')
Then this is how you assign an author in your view action:
def post(self, request):
current_user = request.user
serializer = serializers.PostSerializer(data={**request.data, author=current_user})
...
I found a way that works fine for me.
Pass the context to the serializer from the view
Post View:
class AddPostView(APIView):
serializer_class = serializers.PostSerializer
def post(self, request):
serializer = serializers.PostSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Then here is the serializer. Post Serializer
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'author')
extra_kwargs = {"author": {"read_only": True}}
def create(self, validated_data):
user = self.context['request'].user
post = Post(
title=validated_data['title'],
content=validated_data['content'],
author=user
)
post.save()
return post
Hi I'm trying to create a form that accepts Log from the user however I don't know how to pass the instance of the user. I'm used to creating a CreateView for this, however since I'm planning to use customized widgets and settings, I'm using a modelform to create logs for the user.
My question is is this the same way as create view to check the instance of the user?
Is it still the same as what I did to my createview which is:
def form_valid(self,form) :
form.instance.testuser = self.request.user
return super().form_valid(form)
Or do I have to do something else entirely?
Here is my Forms.py:
from django import forms
from profiles.models import User
from .models import DPRLog
class DateInput (forms.DateInput):
input_type = 'date'
class Datefield (forms.Form):
date_field=forms.DateField(widget=DateInput)
class dprform(forms.ModelForm):
class Meta:
model = DPRLog
widgets = {'reportDate':DateInput()}
fields = ['status','login','logout','reportDate','mainTasks','remarks']
Models.py:
from django.db import models
from profiles.models import User
from django.urls import reverse
# Create your models here.
class Points(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
points = models.IntegerField(default=0, null=False)
def __str__(self):
return self.user.username
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.png', upload_to='profile_pics')
def __str__(self):
return f'{self.user.username} Profile'
class Manager(models.Model):
manager = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return self.manager.full_name
class Member(models.Model):
manager = models.ForeignKey(Manager, on_delete=models.CASCADE)
member = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30, null=True)
def __str__(self):
return self.member.full_name
class Job(models.Model):
manager = models.ForeignKey(Manager, on_delete=models.CASCADE)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
title = models.CharField(max_length=30, blank=False, null=False)
description = models.TextField()
datePosted = models.DateTimeField(auto_now=True)
file = models.FileField(null=True, blank=True, upload_to='job_files')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('job-detail', kwargs={'pk': self.pk})
class DPRLog(models.Model):
STATUS_CHOICES = (
('PENDING', 'PENDING'),
('CANCELLED', 'CANCELLED'),
('COMPLETED', 'COMPLETED'),
)
TASKS_CHOICES = (
('TESTS EXECUTION', 'TESTS EXECUTION'),
('TESTS DESIGN', 'TESTS DESIGN'),
('MOBILE TESTING WORKSHOP', 'MOBILE TESTING WORKSHOP'),
('BENCH ACTIVITY', 'BENCH ACTIVITY'),
('DEFECT ANALYSIS','DEFECT ANALYSIS'),
)
testuser = models.ForeignKey(User,on_delete = models.CASCADE)
status = models.CharField(max_length=30, choices=STATUS_CHOICES,null=True)
reportDate = models.DateField(blank=False, null=False)
login = models.TimeField(blank=False, null=False)
logout = models.TimeField(blank=False, null=False)
mainTasks = models.CharField(max_length=50, blank=False, choices=TASKS_CHOICES, null=True)
remarks = models.CharField(max_length=30,null=True)
def __str__(self):
return f'{self.testuser.full_name} DPR Log'
Views.py:
def dprmodelform(request):
if request.method=='POST':
form = dprform(request.POST)
if form.is_valid():
form.save()
form = dprform()
return render (request,'users/dprform.html',{'form':form})
def form_valid(self,form) :
form.instance.testuser = self.request.user
return super().form_valid(form)
class dprview(LoginRequiredMixin,ListView):
model = DPRLog
template_name = 'users/dpr_view.html'
context_object_name = 'log'
If you pass commit=False to form.save() you can get the instance from the validated form without saving to the database. You can then set the user attribute on the instance before calling save again
if form.is_valid():
instance = form.save(commit=False)
instance.testuser = request.user
instance.save()
This question already has answers here:
Queryset for current logged in user Django
(4 answers)
Closed 3 years ago.
I have API view which show me objects filtered by id and everything is ok but I cant change this view to filter by already logged user, model have ForeignKey related to User and I want for this View to show only objects that are related to already logged user.
This is my view for id which work:
class CreateComment(APIView):
def get_object(self, id):
try:
return Product.objects.get(id=id)
except Product.DoesNotExist:
raise Http404
def get(self,request, id):
product = self.get_object(id)
serializer = ProductSerializer(product)
return Response(serializer.data)
def post(self, request, id):
serializer = CommentSerializer(data=request.data)
if serializer.is_valid():
serializer.save(nick=request.user, product=self.get_object(id))
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I tried to do something like this:
def get_object(self):
try:
return Product.objects.filter(user=request.user)
except Product.DoesNotExist:
raise Http404
def get(self):
product = self.get_object()
serializer = ProductSerializer(product)
return Response(serializer.data)
And something like this:
def get(self, request):
product = Product.objects.filter(user=request.user)
serializer = ProductSerializer(product)
return Response(serializer.data)
But nothing work,
I know about get_queryset but this is for generic Views, or maybe it is also for custom views but I don't know how use this in my view
#Models.py
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, unique=True,)
class Meta:
ordering = ('name',)
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('product_list', args=[self.slug])
lass Product(models.Model):
STATUS_CHOICES = (('available', 'dostępny'),
('unavailable', 'niedostępny'))
category = models.ForeignKey(Category, on_delete=models.CASCADE, default=None, related_name='products')
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, unique=True,)
description = models.TextField()
photo = models.ImageField(upload_to='photos/', default=None)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.CharField(max_length=11, choices=STATUS_CHOICES,
default='unavailable')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
class Meta:
ordering = ('name',)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('product_detail', args=[self.id, self.slug])
class Comment(models.Model):
STATUS_CHOICES = (('1/5', '1'),
('2/5', '2'),
('3/5', '3'),
('4/5', '4'),
('5/5', '5'),)
nick = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
rate = models.CharField(max_length=3, choices=STATUS_CHOICES, default=None)
content = models.TextField(max_length=250)
product = models.ForeignKey(Product, related_name='comments',
on_delete=models.CASCADE, default=None)
published = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('published',)
def __str__(self):
return f'Komentarz wstawiony przez {self.nick} do produktu {self.product}'
This should generate a list of products for you. The key difference is the many=True in ProductSerializer.
class ProductView(APIView):
def get_queryset(self):
return Product.objects.filter(user=self.request.user)
def get(self,request, id):
products = self.get_queryset()
serializer = ProductSerializer(products, many=True)
return Response(serializer.data)