i'm fighting with DRF too long so now i must ask question.. How change ForeignKey to another? I have user profile and relation to status model.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ldap_uid = models.CharField(max_length=100, blank=True, null=True, default=None)
redmine_id = models.IntegerField(blank=True, null=True, default=None)
status = models.ForeignKey(Status, models.SET_NULL, blank=False, null=True, default=DEFAULT_STATUS_ID)
location = models.ForeignKey(Location, models.SET_NULL, blank=False, null=True, default=DEFAULT_LOCATION_ID)
online = models.BooleanField(default=False)
class SelectValuesModel(models.Model):
name = models.CharField(max_length=100)
display_name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Meta:
abstract = True
class Status(SelectValuesModel):
pass
class Location(SelectValuesModel):
pass
What is good way to change Profile status to another? I'm trying with something like this without success
views.py
class UserStatusView(viewsets.ViewSet):
def partial_update(self, request, pk=None):
user = User.objects.get(pk=pk)
user_profile = user.profile
new_stauts = Status.objects.get(request.data.status)
serialized_data = ProfileSerializer(user_profile)
if(serialized_data.is_valid()):
serialized_data.save(status=new_stauts)
return Response(serialized_data.errors)
And trying send new id via PATCH. I'm trying tto find solution but no success here too. And how do it good? Make another route for updating Profile status? Or make something like profile/1/update_status/2? Now my routing looks like:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'redmine', views.RedmineCurrentTaskView, base_name='redmine')
router.register(r'parameters', views.ParametersView, base_name='parameters')
router.register(r'update_status', views.UserStatusView, base_name='update_status')
router.register(r'debug', views.DebugStatus, base_name='debug')
urlpatterns = [
path('', views.index, name='index'),
path('api/', include(router.urls))
]
And serializers.py
class SelectValuesSerializer(serializers.ModelSerializer):
class Meta:
fields = ('pk', 'name', 'display_name')
class LocationSerializer(SelectValuesSerializer):
class Meta(SelectValuesSerializer.Meta):
model = Location
class StatusSerializer(SelectValuesSerializer):
class Meta(SelectValuesSerializer.Meta):
model = Status
class ProfileSerializer(serializers.ModelSerializer):
status = StatusSerializer()
location = LocationSerializer()
class Meta:
model = Profile
fields = ('status', 'location', 'online', 'redmine_id')
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('pk', 'first_name', 'profile')
read_only_fields = ('first_name',)
Just pass request.data to the serializer with partial=True argument:
class UserStatusView(viewsets.ViewSet):
def partial_update(self, request, pk=None):
user = User.objects.get(pk=pk)
user_profile = user.profile
serialized_data = ProfileSerializer(user_profile, data=request.data, partial=True)
if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
return Response(serialized_data.errors)
You need to provide status_id with request body like this:
{"status": 1}
UPD
To pass status as id change your serializer to this:
class ProfileSerializer(serializers.ModelSerializer):
location = LocationSerializer()
class Meta:
model = Profile
fields = ('status', 'location', 'online', 'redmine_id')
def to_representation(self, instance):
self.fields['status'] = StatusSerializer()
return super(ProfileSerializer, self).to_representation(instance)
This allows to post status_id, but get status details with your API.
Related
I am trying to add Value into InstantInvestment Model in Django REST Framework which is working. but, only want to show the shipping that is specifically for the login user in. which means, the present situation is giving all the shipping not for this user.
models.py
class Shipping(models.Model):
investor = models.ForeignKey(User, related_name='shipping', on_delete=models.CASCADE)
beneficiary = models.CharField("Beneficiary Name", max_length=150)
bank = models.ForeignKey(Bank, related_name="bank", on_delete=models.CASCADE)
account = models.CharField(max_length=10)
address = models.TextField("Shipping Adresss")
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.beneficiary
class Meta:
verbose_name = 'Shipping'
verbose_name_plural = 'Shippings'
class InstantInvestment(models.Model):
investor = models.ForeignKey(User, related_name='instantivestment', on_delete=models.CASCADE)
ref_code = models.CharField(max_length=200, blank=True, null=True)
investment = models.FloatField("Investment in dollar")
rate = models.FloatField("Exchange Rate")
transferable = models.FloatField("Money Transferable")
conversion = models.FloatField("Rate in Naira")
product = models.ForeignKey(Product, related_name='instant_product', on_delete=models.CASCADE)
shipping = models.ForeignKey(Shipping, related_name='shipping', on_delete=models.CASCADE)
done = models.BooleanField("Completed Transaction", default=False)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.investor.get_full_name()} - Transaction Code: {self.ref_code}'
class Meta:
verbose_name = 'InstantInvestment'
verbose_name_plural = 'InstantInvestments'
serializers.py
class ShippingSerializer(serializers.ModelSerializer):
class Meta:
model = Shipping
fields = ('beneficiary', 'bank', 'account', 'address')
class QucikPaymentSerializer(serializers.ModelSerializer):
class Meta:
model = InstantInvestment
fields = ('url', 'id','investment', 'rate', 'transferable', 'conversion', 'product', 'shipping')
views.py
class QuickPaymentView(viewsets.ModelViewSet):
queryset = InstantInvestment.objects.all()
serializer_class = QucikPaymentSerializer
permission_classes = [ permissions.IsAuthenticated ]
def get_queryset(self):
return InstantInvestment.objects.filter(investor=self.request.user, done=False)
def perform_create(self, serializer):
serializer.save(investor=self.request.user)
Remove the query set attribute in your viewset class
class QuickPaymentView(viewsets.ModelViewSet):
serializer_class = QucikPaymentSerializer
permission_classes = [ permissions.IsAuthenticated ]
def get_queryset(self):
return InstantInvestment.objects.filter(investor=self.request.user, done=False)
def perform_create(self, serializer):
serializer.save(investor=self.request.user)
to make it work you need to specify the basename key word argument when you register your viewset class with router.
router.register(r'quickpayment/' , QuickPaymentView , basename='InstantInvestment')
If you want do so you need to write a html separately. I think that you showed in your question is rest frameworks ui to test the api. That UI can't determine the User before you send the request.
i have the below config and i would like to map the url field in UserProfileView to the related user's username instead of the default pk field
currently the url looks likes below, appreciate any help
{
"user": 23,
"bio": "My bio",
"created_on": "2020-06-12T21:24:52.746329Z",
"url": "http://localhost:8000/bookshop/bio/8/?format=api"
},
what i am looking for is
{
"user": 23, <--- this is the user <pk>
"bio": "My bio",
"created_on": "2020-06-12T21:24:52.746329Z",
"url": "http://localhost:8000/bookshop/bio/username/?format=api"
},
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
views.py
class UserProfileViewSets(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication, ]
permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
# lookup_field = 'user.username'
def perform_create(self, serializer):
serializer.save(user=self.request.user)
serializer.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserProfile
fields = ['user', 'bio', 'created_on', 'url']
extra_kwargs = {
'last_updated': {
'read_only': True
},
'user': {
'read_only': True
},
}
after struggling and reading many articles, I did it and posting down the solution if anybody was looking for the same use case.
the fields are being related to each other by OneToOne relationship
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
class User(AbstractBaseUser, PermissionsMixin):
""""
Customizes the default user account
"""
email = models.EmailField(unique=True, help_text='username is the email address')
first_name = models.CharField(max_length=40, blank=False)
last_name = models.CharField(max_length=40, blank=False)
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
username = models.CharField(max_length=15, unique=True, null=True, blank=False,
validators=(validators.UnicodeUsernameValidator, ))
is_borrower = models.BooleanField(default=False)
The serializer is a HyperlinkedModelSerializer, as shown below the user SerializerField is PrimaryKeyRelatedField and it is being related to another column/field in the User model user.username - i made this as the default PrimaryKeyRelatedField is the pk and i dont want to expose that on the API
the url key is customized to be HyperlinkedRelatedField to point to the above field - the user with a viewname user-related
serializer.py
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.PrimaryKeyRelatedField(source='user.username', read_only=True)
url = serializers.HyperlinkedRelatedField(read_only=True, view_name='user-detail', )
class Meta:
model = models.UserProfile
fields = ['user', 'bio', 'created_on', 'url']
extra_kwargs = {
'last_updated': {
'read_only': True
},
'user': {
'read_only': True
},
}
on the views, i defined the lookup_field to be user and override the get_object method as now the queryset should be filtered by the username
views.py
class UserProfileViewSets(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication, ]
permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
lookup_field = 'user'
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_object(self):
queryset = self.filter_queryset(models.UserProfile.objects.get(user__username=self.kwargs.get('user')))
return queryset
EDIT:
I did the requirements in another approach and think this one is more neat way , so below the modifications.
You need to create anew customized HyperLinkedIdentityField where you over right the kwargs, check the below kwargs, the value is mapped to the related model where a OneToOneForgienKey deifined
class AuthorHyperLinkedIdentityField(serializers.HyperlinkedIdentityField):
def get_url(self, obj, view_name, request, format):
if hasattr(obj, 'pk') and obj.pk is None:
return None
return self.reverse(view_name, kwargs={
'obj_username': obj.author.username
}, format=format, request=request)
on the view you overright the lookup_field with the kwargs defined in the CustomizedField
class AuthorViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AuthorSerializer
queryset = models.Author.objects.all()
renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.AdminRenderer]
# the below is not used but i keep it for reference
# lookup_field = 'author__username'
# the below should match the kwargs in the customized HyperLinkedIdentityField
lookup_field = 'obj_username'
The final serializer would look like
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
"""
Serializers Author Model
"""
# first_name = serializers.SlugRelatedField(source='author', slug_field='first_name',
# read_only=True)
# last_name = serializers.SlugRelatedField(source='author', slug_field='last_name',
# read_only=True)
author = serializers.PrimaryKeyRelatedField(queryset=models.User.objects.filter(groups__name='Authors'),
write_only=True)
name = serializers.SerializerMethodField()
username = serializers.PrimaryKeyRelatedField(source='author.username', read_only=True)
# the below commented line is building the URL field based on the lookup_field = username
# which takes its value from the username PrimaryKeyRelatedField above
# url = serializers.HyperlinkedRelatedField(view_name='user-detail', read_only=True)
url = AuthorHyperLinkedIdentityField(view_name='author-detail', read_only=True)
class Meta:
model = models.Author
fields = ['author', 'name', 'username', 'url', ]
def get_name(self, author):
return '%s %s' % (author.author.first_name, author.author.last_name)
below the Author Model for your reference
class Author(models.Model):
"""
A Model to store the Authors info
"""
author = models.OneToOneField(User, on_delete=models.CASCADE, related_name='authors')
is_author = models.BooleanField(default=True, editable=True, )
class Meta:
constraints = [
models.UniqueConstraint(fields=['author'], name='check_unique_author')
]
def __str__(self):
return '%s %s' % (self.author.first_name, self.author.last_name)
def author_full_name(self):
return '%s %s' % (self.author.first_name, self.author.last_name)
I want to make a Django rest api that is user specific so that I do /username at the end of the url.
Models:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.IntegerField()
description = models.CharField(max_length=300)
class Meta:
verbose_name_plural = 'User Profiles'
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_data(sender, update_fields, created, instance, **kwargs):
if created:
user = instance
profile = UserProfile.objects.create(user=user, age=18, description='No Description')
class Notes(models.Model):
note = models.CharField(max_length=1000)
parent_user = models.OneToOneField(UserProfile, blank=True, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'Notes'
def __str__(self):
return self.note
Serializers:
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Notes
fields = ('id', 'note', 'parent_user')
urls:
router = routers.DefaultRouter()
router.register('notes', views.UserNoteView)
urlpatterns = [
path('', include(router.urls)),
]
views:
class NoteView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Notes.objects.all()
serializer_class = NoteSerializer
class UserNoteView(NoteView):
def get_queryset(self):
return self.request.parent_user.Notes.all()
My problem is that I can't do for example /William which is the name of my user, and user profile. Someone that knows this must be able to help!
Your notes model is incorrect. Change OneToOneField to ForeignKey. You can change it as below
class Notes(models.Model):
note = models.CharField(max_length=1000)
parent_user = models.ForeignKey(
UserProfile, blank=True, on_delete=models.CASCADE, related_name="notes")
def __str__(self):
return "{note}".format(note=self.note)
Now, change your viewset as below
class UserNoteView(NoteView):
def get_queryset(self):
return self.request.user.userprofile.notes.all()
# or
return Notes.objects.filter(parent_user__user=self.request.user)
I am creating sample-api which have posts and followers. Post should visible to followers only
My models.py
from django.contrib.auth.models import User
class Post(models.Model):
creator = models.ForeignKey(User, related_name='creator_post_set', null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=25)
created_date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
likes = models.BigIntegerField(null=True)
comments = models.BigIntegerField(null=True)
class Follow(models.Model):
follower = models.ForeignKey(User, related_name='following', null=True, on_delete=models.CASCADE)
followed_on = models.DateTimeField(auto_now_add=True)
following = models.ForeignKey(User, related_name='follower',null=True, on_delete=models.CASCADE)
My serializers.py for the models:
class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
)
return user
class Meta:
model = User
fields = ('password', 'username', 'first_name', 'last_name',)
class PostListSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'content', 'created_date',]
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
fields = '__all__'
My views.py:
class PostList(generics.ListCreateAPIView):
serializer_class = PostListSerializer
follow_model = FollowSerializer.Meta.model
post_model = PostSerializer.Meta.model
def get_queryset(self):
try:
followers = self.follow_model.objects.get(follower_id =
self.request.user.id)
queryset = self.post_model.objects.get(creator__in = followers)
except self.follow_model.DoesNotExist:
queryset = None
return queryset
When I call this view it returns the following error:
Cannot query "Follow object (1)": Must be "User" instance.
I need help Thanks in Advance.
As I can see, Post model's creator is FKed to User model. So you need to query using User model instance, not Follower model.
You can use the following code:
following = self.request.user.following.all().values_list('follower', flat=True) # because of related name
queryset = self.post_model.objects.filter(creator_id__in = list(following))
Here I have first retrieved the user ids using self.request.following.all() by reverse relationship. Then I have extracted the user ids using values_list. After that, I have used it in Post.objects.filter(...) method.
At the moment I developed the following code, for me to get the Contact List of each user. The views return the ID numbers of the Contacts of the User. I need to get, instead of the ID numbers, the 'name' and 'last_name' attribute of said contacts. I am quite new to Django's REST Framework and I'm not quite sure what to do next but I believe I have to nest the APIView. I would really appreciate some help!
views.py
def list_contacts(request, id_profile):
url = request.build_absolute_uri(reverse('api_users:contact_list', kwargs={'pk':id_profile}))
response = requests.get(url)
profile = Profile.objects.get(pk=id_profile)
if response.status_code == status.HTTP_200_OK:
data = response.content
user = json.loads(data)
return render(request, 'profiles/contact_list.html', {'user':user})
models.py
class Profile(models.Model):
id_user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birthday = models.DateField(auto_now=False)
description = models.CharField(max_length=100)
profile_picture = models.ImageField(upload_to='images/profiles/%Y/%m/%d', blank=False)
active = models.BooleanField(default = False)
contacts = models.ManyToManyField('self', blank=True, default='null')
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
deleted_at = models.DateTimeField(blank=True, null=True)
class Meta:
ordering = ('-id',)
def __str__(self):
return self.name+' '+self.last_name
def active_profiles():
return Profile.objects.filter(active=True)
api/views.py
class ContactListView(generics.ListAPIView):
queryset = Profile.objects.all()
serializer_class = UserContactListSerializer
filter_backends = (filters.SearchFilter,)
search_fields = ('name', 'last_name',)
def get(self, request, pk, format=None):
contacts = Profile.objects.get(pk=pk)
serializer = UserContactListSerializer(contacts)
return Response(serializer.data)
api/serializers.py
class UserContactListSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['name','last_name','contacts']
I don't know what exactly is going on in your list_contacts but if you want to use the same serializer as a field in itself, you currently can't.
While Django models allow you to use 'self' as the reference, DRF doesn't.
What you can instead do is create another serializer and use that as the field.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("id", "first_name", "last_name")
class UserContactListSerializer(serializers.ModelSerializer):
contacts = UserSerializer(many=True)
class Meta:
model = Profile
fields = ("id", "first_name", "last_name", "contacts")