Unique ManyToManyField DRF - django

I do not want to be able to create multiple Chat objects with the same EXACT participants field.
For example:
If a chat already exists, with participants=["user1", "user2"],
I do not want to be able to create a new chat objects with the same EXACT participants
Looking for something like unique=True, except for manytomanyfield.
Models:
class Contact(models.Model):
user = models.ForeignKey(
User, related_name='friends', on_delete=models.CASCADE)
friends = models.ManyToManyField('self', blank=True)
def __str__(self):
return self.user.username
class Message(models.Model):
contact = models.ForeignKey(
Contact, related_name="messages", on_delete=models.CASCADE)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.contact.user.username
class Chat(models.Model):
participants = models.ManyToManyField(
Contact, related_name='chats')
messages = models.ManyToManyField(Message, blank=True)
def __str__(self):
return f"{self.pk}"
Serializer:
class ContactSerializer(serializers.StringRelatedField):
def to_internal_value(self, value):
return value
class ChatSerializer(serializers.ModelSerializer):
participants = ContactSerializer(many=True)
class Meta:
model = Chat
fields = ("id", "messages", "participants")
read_only = ('id')
def create(self, validated_data):
print(validated_data)
participants = validated_data.pop('participants')
# for c in Chat.participant_set.all():
# print(c)
chat = Chat()
chat.save()
for username in participants:
contact = get_user_contact(username)
chat.participants.add(contact)
chat.save()
return chat

Probably you can try like this:
participants = validated_data.pop('participants')
prev_chat = Chat.objects.all()
for username in participants:
prev_chat = prev_chat.filter(participants__username=username)
if prev_chat.exists():
chat = prev_chat.first()
else:
chat = Chat()
chat.save()
for username in participants:
contact = get_user_contact(username)
chat.participants.add(contact)

Related

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

Want to send extra data from BasePermission to Queryset in ModelVIewSet DRF

Here is My Seller Model which has User as OneToOneField
#models.py
.
.
class CompanyProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="user")
company = models.CharField(max_length=255)
pincode = models.IntegerField()
city = models.ForeignKey(City, on_delete=models.CASCADE)
address = models.TextField()
profileActive = models.BooleanField(default=False)
.
.
Purchase Model
#models.py
class PurchaseOrder(models.Model):
company = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE)
message = models.TextField(blank=True, null=True)
expectedDelivery = models.DateField(blank=True, null=True)
isCancel = models.BooleanField(default=False))
updated_at = models.DateTimeField(auto_now=True)
#Custom permission_classes
class SellerCompanyActive(BasePermission):
message = 'Only Seller with activated company account is allowed'
def has_permission(self, request, view):
user = AuthUser.objects.get(id=request.user.id)
if user.is_seller:
try:
company = CompanyProfile.objects.get(user=user)
if company.profileActive:
return True
else:
self.message = "Company profile is not active"
except CompanyProfile.DoesNotExist:
self.message = "Company not found first create company"
return False
In ModelViewSet
#views.py
class SellerPurchaseOrder(viewsets.ModelViewSet):
queryset = PurchaseOrder.objects.all()
serializer_class = PurchaseOrderSerializer
authorization_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated, SellerCompanyActive]
def get_queryset(self):
user = self.request.user
company = CompanyProfile.objects.get(user=user)
return self.queryset.filter(company=company)
Now here I always had to use this
user = self.request.user
company = CompanyProfile.objects.get(user=user)
As their are lot of other views also, Is their any other way to send data from my custom permission_classes i.e from SellerCompanyActive to direct SellerPurchaseOrder->get_queryset
You can set message attribute to be a dict, something like this:
class SellerCompanyActive(BasePermission):
message = {'error': 'Only Seller with activated company account is allowed'}
def has_permission(self, request, view):
...
you can add more fields to the dictionary to send more data

Django saveing logged in logged in user as ForeignKey

I have model with a ForeignKey "log_written_by" and I want that to be the logged in user.
How should i state that in my forms.py as a hiddenfield?
class AssetLog(models.Model):
# Relationships
log_written_by = models.ForeignKey("auth.User", on_delete=models.SET_NULL, blank=True, null=True)
asset_case = models.ForeignKey("asset_app.AssetCase", on_delete=models.CASCADE)
# Fields
date_time_log = models.DateTimeField()
notes = models.TextField(max_length=1024)
created = models.DateTimeField(auto_now_add=True, editable=False)
class Meta:
pass
def __str__(self):
return str(self.pk)
def get_absolute_url(self):
return reverse("asset_app_AssetLog_detail", args=(self.pk,))
def get_update_url(self):
return reverse("asset_app_AssetLog_update", args=(self.pk,))
You can do the following:
class AssetLogForm(forms.ModelForm):
class Meta:
model = AssetLog
fields = ('log_written_by ', '...,')
widgets = {'log_written_by ': forms.HiddenInput()}
See this related StackOverflow post here.

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 GET from a model, showing its related models' details in the JSON response?

I am writing a small system of which a Transaction has an Account and Category related to it. Currently I'm using PrimaryKeyRelatedField in the TransactionSerializer. I need to, when GETing all the transactions or just one, to return the related Account and Category details in the JSON response. By using PrimaryKeyRelatedField, the response is alike this:
class TransactionSerializer(serializers.ModelSerializer):
account = serializers.PrimaryKeyRelatedField(read_only=True)
category = serializers.PrimaryKeyRelatedField(read_only=True)
# output:
[
transaction: {
id: 1,
account: 1
category: 3,
...
},
...
]
To bring the details the related account and category, I've done the following in the TransactionSerializer:
class TransactionSerializer(serializers.ModelSerializer):
account = AccountSerializer()
category = CategorySerializer()
# output:
[
transaction: {
id: 1,
account: { id: 1, name: "Foo", ... }
category: { id: 3, name: "Bar", ... },
...
},
...
]
But then I cannot create a new Transaction. It shows an error saying account and category are required. I've tried moving the fields to read_only within the serializer, but then another error says these fields should be within fields list.
Full views.py of transactions:
class TransactionList(APIView):
def get(self, request):
user_id = request.user.pk
transactions = Transaction.objects.filter(user_id=user_id).order_by('-created_at')
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
def post(self, request):
account = Account.objects.get(pk=request.data['account'])
category = Category.objects.get(pk=request.data['category'])
serializer = TransactionSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, account=account, category=category)
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
class TransactionDetail(APIView):
def get(self, request, pk):
try:
transaction = Transaction.objects.get(pk=pk)
serializer = TransactionSerializer(transaction)
return Response(serializer.data)
except:
raise Http404
urls.py:
urlpatterns = [
url(r'^$', views.TransactionList.as_view(), name="transaction_list"),
url(r'^(?P<pk>[0-9]+)/$', views.TransactionDetail.as_view(), name="transaction"),
]
models.py:
class Transaction(models.Model):
user = models.ForeignKey(User, default=None)
account = models.ForeignKey(Account, default=None)
category = models.ForeignKey(Category, default=None)
name = models.CharField(max_length=32)
amount = models.DecimalField(max_digits=10, decimal_places=2, default="0.0")
description = models.CharField(max_length=256, default=None, null=True)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
So this is my problem. How do I create a new Transaction, assigning an account and category by ID, and yet when retrieving all, bring all the details?
Creating two serializers, one for listing and one for creating did the trick.
class ListTransactionSerializer(serializers.ModelSerializer):
account = AccountSerializer()
category = CategorySerializer()
class Meta:
model = Transaction
fields = '__all__'
class CreateTransactionSerializer(serializers.ModelSerializer):
account = serializers.PrimaryKeyRelatedField(read_only=True)
category = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Transaction
fields = '__all__'
def create(self, validated_data):
"""
Create and return a new 'Transaction' instance
:param validated_data:
:return: Transaction
"""
return Transaction.objects.create(**validated_data)
def update(self, instance, validated_data):
pass
First rewrite models to related name fileds for rest implementation.
class Transaction(models.Model):
user = models.ForeignKey(User, default=None, related_name="user_serialzer")
account = models.ForeignKey(Account, default=None, related_name="account_serialzer")
category = models.ForeignKey(Category, default=None, related_name="category_serialzer")
name = models.CharField(max_length=32)
amount = models.DecimalField(max_digits=10, decimal_places=2, default="0.0")
description = models.CharField(max_length=256, default=None, null=True)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
Then serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fileds = '__all__'
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fileds = '__all__'
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fileds = '__all__'
class SerializerTransactions(serializers.ModelSerializer):
user_serialzer = UserSerializer(many=True)
account_serialzer = AccountSerializer(many=True)
category_serialzer = CategorySerializer(many=True)
class Meta:
model = Transaction
fileds = ('id', 'name', 'user_serialzer', 'account_serialzer', 'category_serialzer',)