Let's say i have a model
class User(models.Model):
username = models.CharField(max_length=20)
class Friends(models.Model):
friendship_creator = models.Foreignkey(user)
other_user= models.Foreignkey(user)
class Activity(models.Model):
PRIVACY = [
(Friends,Friends),
(Public,Public)
]
activity_creator = models.Foreignkey(User)
name = models.CharField(_("Activity Name"),max_length=100)
privacy = \
models.CharField(_("Privacy"),max_length=15,choices=PRIVACY)
now if a user creates activities with privacy set to friends and also creates some activities where privacy is set to public.So here how can i maintain the privacy in DRF ,,like only friends will be able to see privacy=friends activities and privacy=public can be seen by everyone
Demo View::
class ActivityListApi(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ActivitySerializer
def get_queryset(self, *args, **kwargs):
user = self.request.user
activities = Activity.objects.all().order_by('id')
return activities
def get_queryset(self, *args, **kwargs):
user = self.request.user
friends = User.get_friends(user)
friends_id = friends.values_list('id',flat=True).distinct()
fr_activities = Activity.objects.filter(
privacy='Friends',creator_id__in=list(set(friends_id))
)
pub_activities = Activity.objects.filter(privacy='Public')
print(fr_activities,pub_activities)
activities = fr_activities | pub_activities
return activities
First, you need to set the related_name in Friends model so that you can get friends from the user model.
class Friends(models.Model):
friendship_creator = models.Foreignkey(user, related_name="friends_source")
other_user= models.Foreignkey(user, related_name="friends_target")
Second, you need to make the query to get the id array of friends.
target_friends = user.friends_source.other_user.values_list('id', flat=True).distinct()
source_friends = user.friends_target.friendship_creator.values_list('id', flat=True).distinct()
all_friends_ids = list(set(target_friends) | set(source_friends))
The activities should be either the privacy ones of friends or public ones. So finally, the code is like the following.
from django.db.models import Q
class ActivityListApi(ListAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ActivitySerializer
def get_queryset(self, *args, **kwargs):
user = self.request.user
queryset = Q(Q(activity_creator__id__in = self.get_friend_ids(user)) & Q(privacy = "Friends")) | Q(privacy = "public")
activities = Activity.objects.filter(queryset).order_by('id')
return activities
def get_friend_ids(self, user):
target_friends = user.friends_source.other_user.values_list('id', flat=True).distinct()
source_friends = user.friends_target.friendship_creator.values_list('id', flat=True).distinct()
return list(set(target_friends) | set(source_friends))
Related
I am learning how to use djangorestframework by building a microblog and I want users to be able to upload multiple (kind of like how twitter works). I got a particular error(check below) after using a particular approach(check code).
I have attached my models.py, serializers.py and views.py file:
MODELS.PY FILE:
class TweetFile(models.Model):
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
file = models.FileField(upload_to='images')
class Tweets(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
texts = models.TextField()
file_content = models.ManyToManyField(TweetFile, related_name='file_content')
date_posted = models.DateTimeField(auto_now_add=True)
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
likes = models.PositiveIntegerField(default=0)
liker = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='user_like')
# link = models.URLField()
class Meta:
# verbose_name = _('my thing')
verbose_name_plural = _('Tweets')
def __str__(self):
return f"{self.texts}"
SERIALIZERS.PY FILE:
class TweetSerializer(serializers.ModelSerializer):
tweep = serializers.SerializerMethodField('get_tweep_username')
likes = serializers.SerializerMethodField('get_tweet_likes')
liker = serializers.StringRelatedField(many=True)
class Meta:
model = Tweets
fields = ['id','texts', 'file_content', 'date_posted', 'tweep', 'likes', 'liker']
extra_kwargs = {
"file_content": {
"required": False,
}
}
VIEWS.PY FILE:
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def create_tweet(request):
user = request.user
if request.method == 'POST':
serializer = TweetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(tweep=user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(serializer.errors, status=status.HTTP_405_METHOD_NOT_ALLOWED)
This particular approach gave me this error in my postman:
{
"file_content": [
"Incorrect type. Expected pk value, received InMemoryUploadedFile."
]
}.
Could anybody tell me what I am doing wrong? or what I need to do? any help will be appreciated, thanks.
Okay, so I found a solution to this issue....and this is the code, hopefully someone finds it helpful:
models.py file
class TweetFile(models.Model):
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
media = models.FileField(upload_to='images')
def __str__(self):
return f"{self.tweep.username}'s media images"
class Tweets(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
texts = models.TextField()
file_content = models.ManyToManyField(TweetFile, related_name='file_content', blank=True, null=True)
date_posted = models.DateTimeField(auto_now_add=True)
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = _('Tweets')
def __str__(self):
return f"{self.texts}"
Serializer.py file
from rest_framework import serializers
from tweets.models import Tweets, TweetFile, Comments
class TweetSerializer(serializers.ModelSerializer):
tweep = serializers.SerializerMethodField('get_tweep_username')
class Meta:
model = Tweets
fields = ['id','texts', 'file_content', 'date_posted', 'tweep']
extra_kwargs = {
"file_content": {
"required": False,
}
}
# function that returns the owner of a tweet
def get_tweep_username(self, tweets):
tweep = tweets.tweep.username
return tweep
Views.py file
#api_view(['POST'])
#permission_classes([IsAuthenticated])
#parser_classes([MultiPartParser, FormParser])
def create_tweet(request):
user = request.user
if request.method == 'POST':
files = request.FILES.getlist('file_content')
if files:
request.data.pop('file_content')
serializer = TweetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(tweep=user)
tweet_qs = Tweets.objects.get(id=serializer.data['id'])
uploaded_files = []
for file in files:
content = TweetFile.objects.create(tweep=user, media=file)
uploaded_files.append(content)
tweet_qs.file_content.add(*uploaded_files)
context = serializer.data
context["file_content"] = [file.id for file in uploaded_files]
return Response(context, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = TweetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(tweep=user)
context = serializer.data
return Response(context, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(serializer.errors, status=status.HTTP_405_METHOD_NOT_ALLOWED)
configure your urls.py files appropriately and test the endpoint on POSTMAN, everything should work fine.
###################### Requirement #############################
/add_value/
payload:
{
"profile_id": "34AB",
"amt": 100.00
}
###################### Viewset ####################################
class cash_viewset(ModelViewSet):
queryset = Cashbalance.objects.all()
serializer_class = SampleSerializer
####################### Models ####################################
MOdel -1 ---
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
profile_id = models.CharField(max_length=50,unique=True)
MOdel -2 ---
class Cashbalance(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE,db_column="profile_id")
remianingAmount = models.FloatField()
amt = models.FloatField()
############################### Serializer ##############################
class SampleSerializer(serializers.ModelSerializer):
profile_id = serializers.CharField(source="profile.profile_id")
remianingAmount = serializers.FloatField(read_only=True)
class Meta:
model = CashBalance
fields = ["profile_id", "amt", "remianingAmount"]
def create(self, validated_data):
profile_dict = validated_data['profile']
userprofile = Profile.objects.get(profile_id=profile_dict['profile_id'])
bank_obj = CashBalance.objects.filter(profile_id=userprofile)
bank_obj.update(amt=validated_data['amt'], remianingAmount=validated_data['amt'])
return bank_obj[0]
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
print(instance.profile.user.username)
result = {"profile_id": data['profile_id'], "name": instance.profile.user.username,
"remianingAmount": data['remianingAmount']}
return result
################################# Question is ######################
Assume that, there is already some data in user,profile and Cashbalance tables
As per the payload given above,Based on the profile_id ,I have to update amt value to 100, for this I have implemented the solution in create method(create method given above), please share your inputs ,I am hoping that update cannot be done in create method, how can i improve this in more good way according to my model designs by using modelviewset
To perform the update, you have to override the update method in the serializer.
def update(self, instance, validated_data):
instance.profile.profile_id
= validated_data["profile"].get('profile_id',instance.profile.profile_id)
instance.amt = validated_data.get('amt', instance.amt)
return instance
in your viewset
def update(self,request,pk=None):
profile_id = request.data.get("profile_id")
profile = Profile.objects.get(profile_id = profile_id)
ser = self.get_serializer_class()(instance=profile.cash_balance , data = request.data)
ser.is_valid(raise_exception=True)
ser.save()
in your CashBalanceModel, pass a related_name
profile = models.OneToOneField(Profile, on_delete=models.CASCADE,db_column="profile_id" , related_name="cash_balance")
check docs: https://www.django-rest-framework.org/api-guide/serializers/
I have an object, whose attributes I would like to update. Right now, I am able to update a single attribute eg: name. But the object has several attributes which include name, contact_name, contact_email and contact_phone_number.The following is what I have at the moment.
In views.py
class MerchantViewSet(GetPrefixedIDMixin, viewsets.ModelViewSet):
"""POST support for /merchants/."""
print ("in MerchantViewSet")
queryset = models.Merchant.objects.all()
serializer_class = serializers.CreateMerchantSerializer
lookup_field = "id"
# lookup_value_regex = f"{models.Merchant.id_prefix}_[a-f0-9]{32}"
lookup_value_regex = ".*"
permission_classes = [permissions.MerchantPermission]
def get_queryset(self):
"""Filter the queryset based on the full merchant name or starting with a letter."""
queryset = models.Merchant.objects.all()
search_param = self.request.query_params.get("search", None)
if search_param:
if search_param.startswith("^"):
queryset = queryset.filter(name__istartswith=search_param[1:])
else:
queryset = queryset.filter(name__icontains=search_param)
return queryset
def update(self, request, *args, **kwargs):
request.data["user"] = request.user.id
print (self.get_object().name)
print (request.data)
merchant = self.get_object()
print (response)
print (merchant.name)
for merchants in models.Merchant.objects.all():
print (merchants.id)
if (merchants.id == merchant.id):
merchants.name = request.data["name"]
merchants.save()
I've tried using
response = super(MerchantViewSet, self).update(request, *args, **kwargs)
from what I read in the DRF documentations but using this just returns error 404 when I run my test. Do I simply have to do with I did with name in my code above, with the other attributes? Or is there a more streamlined way to do this?
This is my test:
class MerchantsViewSetTest(tests.BigETestCase): # noqa
#classmethod
def setUpClass(cls): # noqa
super(MerchantsViewSetTest, cls).setUpClass()
cls.application = tests.get_application()
tests.create_group("merchant")
cls.consumer_user = tests.create_consumer()
cls.admin = tests.create_administrator()
cls.merchant_geraldine = models.Merchant.objects.create(
name="Test Account 1",
contact_name="Geraldine Groves",
contact_email="geraldine#example.com",
contact_phone_number="+35310000000",
)
cls. merchant_barbara = models.Merchant.objects.create(
name="Account 2",
contact_name="Barbara",
contact_email="barbara#example.com",
contact_phone_number="+35310000432",
)
def test_edit_merchant(self): # noqa
url = reverse("bige_transactions:merchant-detail", kwargs={'id': self.merchant_geraldine.prefixed_id})
# payload
data = {"name": "Edited"}
# Put data
resp_data = self.put(url, data, user=self.admin, status_code=200)
# Check if data was updated
url = reverse("bige_transactions:merchant-list")
# Try without authenticated user
self.get(url, status_code=401)
# Try with authenticated admin user
resp_data = self.get(url, user=self.admin, status_code=200)
print (resp_data)
I'm trying to get a proof of concept of Django Rest Framework on getstream working to power inapp notifications.
My code https://github.com/morenoh149/django-rest-framework-getstream
models.py
class Snippet(models.Model, Activity):
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(
choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(
choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey(
'auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
#property
def activity_actor_attr(self):
return self.owner
#property
def activity_notify(self):
return [feed_manager.get_notification_feed(self.owner_id)]
class Meta:
ordering = ('created_at', )
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(
style=self.style, linenos=linenos, full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
serializers.py
from django.contrib.auth.models import User from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(
view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code',
'linenos', 'language', 'style')
class ActivitySerializer(serializers.Serializer):
id = serializers.UUIDField()
foreign_id = serializers.CharField()
verb = serializers.CharField()
time = serializers.DateTimeField()
def __init__(self, *args, **kwargs):
object_serializer = kwargs.pop("object_serializer", None)
actor_serializer = kwargs.pop("actor_serializer", None)
super().__init__(self, *args, **kwargs)
if object_serializer:
self.fields["object"] = object_serializer()
else:
self.fields["object"] = serializers.CharField()
if actor_serializer:
self.fields["actor"] = actor_serializer()
else:
self.fields["actor"] = serializers.CharField()
class AggregatedSerializer(ActivitySerializer):
group = serializers.CharField()
activities = ActivitySerializer(many=True)
class NotificationSerializer(AggregatedSerializer):
is_seen = serializers.BooleanField()
is_read = serializers.BooleanField()
def get_activity_serializer(data, object_serializer=None, actor_serializer=None, **kwargs):
kwargs["object_serializer"] = object_serializer
kwargs["actor_serializer"] = actor_serializer
serializer = ActivitySerializer
if "is_seen" in data:
serializer = NotificationSerializer
elif "activities" in data:
serializer = AggregatedSerializer
return serializer(data, **kwargs)
views.py
class NotificationViewSet(viewsets.ViewSet):
"""
This viewset returns a notifications feed for the logged in user.
The feed contains events for when a relevant snippet is created.
"""
serializer_class = NotificationSerializer
def list(self, request):
feeds = feed_manager.get_news_feeds(self.request.user.id)
activities = feeds.get('timeline_aggregated').get()['results']
enriched_activities = enricher.enrich_aggregated_activities(activities)
serializer = get_activity_serializer(enriched_activities, SnippetSerializer, None, many=True)
return Response(serializer.data)
How do I get the /notifications/ endpoint to return the notifications for the signed in user?
In my app, i have a chained dropdown in which i am getting the second dropdown via jquery ajax, which works well.So i am trying to edit this saved data and load it back to an edit form, but the dropdown is showing empty. This is what i have done so far
Here is my model.py
class SchoolFees(models.Model):
fid = models.ForeignKey(FacultyData, on_delete= models.SET_NULL, null=True)
did = models.ForeignKey(DepartmentData, on_delete= models.SET_NULL, null=True)
sid = models.ForeignKey(SessionData, on_delete= models.SET_NULL, null=True)
amount = models.CharField(max_length=30)
def __str__(self):
return self.amount
forms.py
class FeesCreationForm(forms.ModelForm):
fid = forms.ModelChoiceField(queryset=FacultyData.objects.all(), empty_label="--Select Faculty--",
widget=forms.Select(attrs={'class': 'form-control'}))
did = forms.ModelChoiceField(queryset=DepartmentData.objects.all(), empty_label="--Select Faculty First--",
widget=forms.Select(attrs={'class': 'form-control'}))
sid = forms.ModelChoiceField(queryset=SessionData.objects.all(), empty_label="--Select Session--",
widget=forms.Select(attrs={'class': 'form-control'}))
class Meta:
model = models.SchoolFees
fields = ['sid', 'fid', 'did', 'amount']
widgets = {
'amount': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter Amount'})
}
def __init__(self, *args, **kwargs):
super(FeesCreationForm, self).__init__(*args, **kwargs)
self.fields['did'].queryset = DepartmentData.objects.none()
# Get did queryset for the selected fid
if 'fid' in self.data:
try:
fd = int(self.data.get('fid'))
self.fields['did'].queryset = DepartmentData.objects.filter(fid_id=fd).order_by('id')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and use empty queryset
Here is my view.py
def edit_fee(request, pk):
app = settings.CONFIG
post = get_object_or_404(SchoolFees, pk=pk)
if request.method == 'POST':
form = FeesCreationForm(request.POST, instance=post)
if form.is_valid():
form.save()
messages.add_message(request, messages.WARNING, "Fees record updated successfully")
return redirect('bursary:create_fee')
else:
# bring edit form out
form = FeesCreationForm(instance=post)
table = FeesTable(SchoolFees.objects.all())
RequestConfig(request, paginate={'per_page': 10}).configure(table)
context = {"form": form, "fees": table, 'app': app}
return render(request, 'editfee.html', context)
I expect that the saved value is pass to the dropdown with other form fields which are already showing
After going through this post, i was able to solve it when reading the comments. All i needed was to add a backward relationship to my init function.
class FeesCreationForm(forms.ModelForm):
fid = forms.ModelChoiceField(queryset=FacultyData.objects.all(), empty_label="--Select Faculty--",
widget=forms.Select(attrs={'class': 'form-control'}))
did = forms.ModelChoiceField(queryset=DepartmentData.objects.all(), empty_label="--Select Faculty First--",
widget=forms.Select(attrs={'class': 'form-control'}))
sid = forms.ModelChoiceField(queryset=SessionData.objects.all(), empty_label="--Select Session--",
widget=forms.Select(attrs={'class': 'form-control'}))
class Meta:
model = models.SchoolFees
fields = ['sid', 'fid', 'did', 'amount']
widgets = {
'amount': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter Amount'})
}
def __init__(self, *args, **kwargs):
super(FeesCreationForm, self).__init__(*args, **kwargs)
self.fields['did'].queryset = DepartmentData.objects.none()
# Get did queryset for the selected fid
if 'fid' in self.data:
try:
fd = int(self.data.get('fid'))
self.fields['did'].queryset = DepartmentData.objects.filter(fid_id=fd).order_by('id')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and use empty queryset
elif self.instance.pk:
self.fields['did'].queryset = self.instance.fid.departmentdata_set.order_by('id')
#backward relation - for this faculty selected, check its deparm
#every department has its faculty
# #in other word, which dept has their foreign key pointing to the current instance of faculty