I made a single function for register and login with mobile and otp. The register part is in the else part of the function, and the if part is the login function. Every time I log in with the already registered number, it makes a new object in the database, and I don't want that. I want to just update the otp part from when the number was registered in the database.
views.py
class RegistrationAPIView(APIView):
permission_classes = (AllowAny,)
serializer_class = ProfileSerializer
def post(self, request):
mobile = request.data['mobile']
data = Profile.objects.filter(mobile = mobile).first()
if data:
serializer = self.serializer_class(data=request.data)
mobile = request.data['mobile']
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
content = {'mobile': instance.mobile, 'otp': instance.otp}
mobile = instance.mobile
otp = instance.otp
print("Success")
send_otp(mobile,otp)
return Response(content, status=status.HTTP_201_CREATED)
else:
return Response({"Error": "Login in Failed"}, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = self.serializer_class(data=request.data)
mobile = request.data['mobile']
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
content = {'mobile': instance.mobile, 'otp': instance.otp}
mobile = instance.mobile
otp = instance.otp
send_otp(mobile,otp)
return Response(content, status=status.HTTP_201_CREATED)
else:
return Response({"Error": "Sign Up Failed"}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['mobile']
def create(self, validated_data):
instance = self.Meta.model(**validated_data)
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
instance.otp = str(random.randint(1000 , 9999))
instance.save()
return instance
models.py
'''
class Profile(models.Model):
mobile = models.CharField(max_length=20)
otp = models.CharField(max_length=6)
'''
I'm assuming here that you're using DRF Serializers. If that's the case, note from the documentation that :
Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class:
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
As an aside, you probably want to do a get_or_create or create_or_update with defaults instead of having the if/else clause.
Option A: First thing first. If there is only and only one mobile number for each Profile you should add a unique constraint for your Profile model as well. Something like:
class Profile(models.Model):
mobile = models.CharField(max_length=20, unique=True)
otp = models.CharField(max_length=6)
Do not forget to make migration and migrate these new changes to your database (also note that there might be some duplicate records in Profile table so if you're not on production server first delete all of your records and then make this migration).
And then make this change to your serializer's create method:
def create(self, validated_data):
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
instance = self.Meta.model.objects.update_or_create(**validated_data, defualts=dict(otp=str(random.randint(1000 , 9999))))[0]
return instance
with update_or_create now you're sure that if the record with specific mobile exists you will update that and if not you will create new one.
Option B: But if you don't want to make this change to your database for any reason you can just simply do this:
def create(self, validated_data):
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
if self.Meta.model.objects.filter(**validated_data).exists():
instance = self.Meta.model.objects.filter(**validated_data).last()
instance.otp = str(random.randint(1000 , 9999))
instance.save()
else:
instance = self.Meta.model(**validated_data)
instance.otp = str(random.randint(1000 , 9999))
instance.save()
return instance
Note that there might be multiple records in your table with same mobile number as long as there isn't any constraint on that model and here we are only updating latest record in your Profile table. I hope these two options solve your problem.
Related
My question is how would I return a custom JsonResponse from a rest-framework serializer?
I have a view with this code:
# Send money to outside of Pearmonie
class sendmoney_external(viewsets.ModelViewSet):
# Database model
queryset = User.objects.all()
# Serializer - this performs the actions on the queried database entry
serializer_class = SendMoneyExternalSerializer
# What field in database model will be used to search
lookup_field = 'username'
My serializer is something like this:
# Serializer for sending money to another Pearmonie user
class SendMoneyExternalSerializer(serializers.ModelSerializer):
# This is the function that runs for a PUT request
def update(self, instance,validated_data):
# Verifies the requesting user owns the database account
if str(self.context['request'].user) != str(instance.username) and not str(self.context['request'].user) in instance.userprofile.banking_write:
raise exceptions.PermissionDenied('You do not have permission to update')
account_number = self.context['request'].data['to_account']
amount = self.context['request'].data['amount']
to_bank = self.context['request'].data['to_bank']
# Creates the order in the database
from_user = BankingTransaction.objects.create(
user = instance,
date = timezone.now(),
from_account_num = instance.userprofile.account_number,
from_account_name = instance.userprofile.company,
to_account = self.context['request'].data['to_account'],
to_bank = to_bank,
amount_transferred = amount,
description = self.context['request'].data['description'],
trans_reference = self.context['request'].data['trans_reference'],
status = self.context['request'].data['status'],
)
instance.userprofile.notification_count += 1
instance.userprofile.save()
# Creates the notification for the supplier in the database
from_notification = Notifications.objects.create(
user=instance,
date=timezone.now(),
read=False,
message=f'You have paid N{amount} to account number {account_number} ({to_bank})'
)
return instance
class Meta:
model = User
fields = ['pk']
This just simple returns the User model's primary key in json... how would I make it return something custom?
I want to make a request to an external api, and send that external api's response in my django's response.
You can use SerializerMethodField for custom logic.
class SendMoneyExternalSerializer(serializers.ModelSerializer):
# This is the function that runs for a PUT request
custom_data = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['pk', 'custom_data']
def get_custom_data(self, obj):
# Your coustom logic here. (obj) represent the User object
return f'{obj.first_name} {obj.username}'
I am creating a simple model with a many-to-many field. The model works fine and I can create model through the admin panel, and I can make a get request to see that model (except that it only returns user IDs instead of the user models/objects). My problem is when creating a post request to create said model.
I get one of the two errors depending on the changes I make, The serializer field might be named incorrectly and not match any attribute or key on the 'str' instance. or AssertionError: You cannot call '.save()' on a serializer with invalid data., either way it has something to do with my serializer. The following is my model,
class Schema(models.Model):
week = models.PositiveIntegerField(primary_key=True,
unique=True,
validators=[MinValueValidator(1), MaxValueValidator(53)],
)
users = models.ManyToManyField(MyUser, related_name="users")
class Meta:
ordering = ('week',)
My View,
class SchemaView(APIView):
permission_classes = (SchemaPermissions,)
def get(self, request):
schemas = Schema.objects.all()
serializer = SchemaSerializer(schemas, many=True)
return Response(serializer.data)
def post(self, request):
data = request.data
serializer = SchemaSerializer(data=data)
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
And my serializer,
class SchemaSerializer(serializers.ModelSerializer):
class Meta:
model = Schema
fields = ('week', 'users')
def create(self, validated_data):
users_data = validated_data.pop('users')
users = MyUser.objects.filter(id__in=users_data)
schema = Schema.objects.create(week=validated_data.week, users=users)
return schema
def update(self, instance, validated_data):
users_data = validated_data.pop('users')
users = MyUser.objects.filter(id__in=users_data)
instance.users.clear()
instance.users.add(*users)
instance.saver()
return instance
The idea is that if a week number already exists then it should call the update() function and then it should simply overwrite the users related to that week number, otherwise it should call create() and create a new week number with relations to the given users. The following is the result of printing the serializer after initializing it in the view.
SchemaSerializer(data={'week': 32, 'users': [1, 2, 3]}):
week = IntegerField(max_value=53, min_value=1, validators=[<UniqueValidator(queryset=Schema.objects.all())>])
users = PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=MyUser.objects.all())
It seems to me that the serializer should be valid for the given model? I am perhaps missing some concepts and knowledge about Django and DRF here, so any help would be greatly appreciated!
First you need set the field for saving users in the SchemaSerializer. And you don't need to customize the create and update method because the logic could be coded in the views.
class SchemaSerializer(serializers.ModelSerializer):
users = UserSerializer(read_only = True, many = True)
user_ids = serializers.ListField(
child = serializers.IntegerField,
write_only = True
)
class Meta:
model = Schema
fields = ('week', 'users', 'user_ids',)
# remove create and update method
And in views.py,
class SchemaView(APIView):
permission_classes = (SchemaPermissions,)
def get(self, request):
...
def post(self, request):
data = request.data
serializer = SchemaSerializer(data=data)
if serializer.is_valid():
input_data = serializer.validated_data
week = input_data.get('week')
user_ids = input_data.get('user_ids')
if Schema.objects.filter(week = week).count() > 0:
schema = Schema.objects.get(week = week).first()
else:
schema = Schema.objects.create(week = week)
schema.users.set(user_ids)
schema.save()
return Response(SchemaSerializer(schema).data, status=status.HTTP_200_OK)
else:
print(serializer.errors)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
And of course, the payload data should be
{'week': 23, 'user_ids': [1,2,3]}
I am making a system saved user data to model.I want to write a part of logic in views.py and a part of save data in serializer.I want to make a system password is changed into hash.Now I wrote codes in views.py,
class InfoViews(viewsets.ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def create(self,request, *args, **kwargs):
user = Info()
passwd = request.data['password']
md5 = hashlib.md5()
md5.update(passwd.encode('utf-8'))
user.password = md5.hexdigest()
user.save()
return JsonResponse({"data":"data"})
in serializer.py
class InfoSerializer(serializers.ModelSerializer):
created_time = serializers.DateTimeField(required=False)
class Meta:
model = Info
fields = '__all__'
def create(self, validated_data):
user = Info(
email=validated_data['email'],
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
in models.py
class Info(models.Model):
username = custom_fields.NotEmptyCharField(max_length=100, unique=True)
email = models.EmailField()
password = custom_fields.NotEmptyCharField(max_length=100)
class Meta:
db_table = 'info'
def __str__(self):
return '%s: %s' % (self.username, self.email)
Now whenI tried to save user data to model,django.core.exceptions.ValidationError: ['Can not be empty!'] error happens.What is wrong in my codes?I searched http://www.django-rest-framework.org/api-guide/serializers/ .How should I fix this?
You are not using InfoSerializer() serializer so, remove create() method from that, and change your views.py as below,
class InfoViews(ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def create(self, request, *args, **kwargs):
serializer = InfoSerializer(request.data).data
serializer.pop('created_time', None)
passwd = serializer['password']
md5 = hashlib.md5()
md5.update(passwd.encode('utf-8'))
serializer['password'] = md5.hexdigest()
Info.objects.create(**serializer)
return JsonResponse({"data": "data"})
My Friendly Suggestion
I don't think this is a good method to acheive so, So changes below will do better (I think so ;))
views.py
class InfoViews(ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
serializer.py
import hashlib
class InfoSerializer(serializers.ModelSerializer):
created_time = serializers.DateTimeField(required=False)
def set_password(self, raw_pwd):
md5 = hashlib.md5()
md5.update(raw_pwd.encode('utf-8'))
return md5.hexdigest()
class Meta:
model = Info
fields = '__all__'
def create(self, validated_data):
validated_data['password'] = self.set_password(validated_data['password'])
return super().create(validated_data)
Update
Alternative create() for serializer,
def create(self, validated_data):
validated_data['password'] = self.set_password(validated_data['password'])
user = Info.objects.create(
email=validated_data['email'],
username=validated_data['username'],
password=validated_data['password']
)
# you can avoid large number of assignment statements (as above) by simply calling "super()" method
return user
You're getting a validation error because email is a required field. When you run user.save(), the email value isn't sent, hence the ValidationError.
You should definitely be saving everything in your view, the Serialiser is just a way to change the way the data is presented by DRF.
Also, you really shouldn't be using md5 to save your passwords. Just use the built in Django method: user.set_password(password) - Django will take care of the hashing for you and much more securely.
guys in my app each product must have only one record for discount in table.
i have this view for create new record in db .. how can i tell if object with comming product id is availible in db just update the target column otherwise create new object ? some people use def create in serializer should i use that function or something else ???
class DiscountControllAPiView(APIView):
"""
each product may takes a discount code or percent from owner or site administrator
"""
def post(self,request):
serializer = ProductDiscountControllSerializer(data=request.data)
if(serializer.is_valid()):
serializer.save()
return Response(True)
else:
return Response(serializer.errors)
related serilizer :
class ProductDiscountControllSerializer(ModelSerializer):
class Meta:
model = ProductDiscountControll
fields = [
'product',
'discount',
'discount_code',
'discount_code_precent',
]
Try this
class DiscountControllAPiView(APIView):
"""
each product may takes a discount code or percent from owner or site administrator
"""
def post(self,request):
id = request.data.get('id', None):
if not id:
# perform creation
serializer = ProductDiscountControllSerializer(data=request.data)
else:
# perform updation
product_discount_controll = ProductDiscountControll.objects.get(id=int(id))
serializer = ProductDiscountControllSerializer(product_discount_controll, data=request.data)
if(serializer.is_valid()):
serializer.save()
return Response(True)
else:
return Response(serializer.errors)
If id is present in the request, the api will update else it will create new record
On my current project I want the user to be able to fill in forms without having to sign up first (to make them more likely to use the service).
On the below view I'm trying to either save the registered user with the form data, or if the user isn't registered save the Session ID as a temporary user id.
However when I try to use the session ID it returns none. I'm not sure why the data is missing? (Session have the default django setup in apps and middleware as per the docs). Note when a user is logged in it seem to have a user id but not when no user is logged in.
View:
class ServiceTypeView(CreateView):
form_class = ServiceTypeForm
template_name = "standard_form.html"
success_url = '/'
def form_valid(self, form):
if self.request.user.is_authenticated():
form.instance.user = self.request.user
else:
form.instance.temp_user = self.request.session.session_key
super().form_valid(form)
online_account = form.cleaned_data['online_account']
if online_account:
return redirect('../online')
else:
return redirect('../address')
Model:
class EUser(models.Model):
supplier1 = models.OneToOneField(SupplierAccount)
supplier2 = models.OneToOneField(SupplierAccount)
supplier3 = models.OneToOneField(SupplierAccount)
online_account = models.BooleanField()
address = models.OneToOneField(Address, null=True)
temp_user = models.CharField(max_length=255, null=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, default=None)
class SupplierAccount(models.Model):
supplier = models.ForeignKey(Supplier)
username = models.CharField(max_length=255)
password = models.CharField(max_length=255)
Form:
class ServiceTypeForm(forms.ModelForm):
# BOOL_CHOICES = ((False, 'No'), (True, 'Yes'))
# online_account = forms.BooleanField(widget=forms.RadioSelect(choices=BOOL_CHOICES))
def __init__(self, *args, **kwargs):
super(ServiceTypeForm, self).__init__(*args, **kwargs)
self.fields['service_type'].initial = 'D'
class Meta:
model = EUser
fields = ('service_type', 'online_account')
The session key will exist if there is data set in the session dictionary already. Logged in users have a session key because Django stores authentication related data in the session by default, so a key will always be assigned because of that.
You can ensure that a key always exists by tossing some data into the session storage before trying to get the key.