How do I upload multiple images using django rest framework? - django-rest-framework

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.

Related

GET privacy(friends/public) on activities maintained on Django Rest Framework

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))

Django TypeError get() argument after ** must be a mapping, not list

I'm creating a serializer for a model with a ManyToManyField (tag_id) that refers to a Tag table.
serializers.py
class CombinationSerializer(serializers.ModelSerializer):
# tag_id = serializers.PrimaryKeyRelatedField(queryset=Tag.objects.all(), source='tag', required=False, many=True)
tag_id = TagWithIdSerializer(many=True, required=False, write_only=False)
resource_id = serializers.PrimaryKeyRelatedField(queryset=Resource.objects.all(),
required=True,
source='resource',
write_only=False)
gameround_id = serializers.PrimaryKeyRelatedField(queryset=Gameround.objects.all(),
required=False,
source='gameround',
write_only=False)
user_id = serializers.PrimaryKeyRelatedField(queryset=CustomUser.objects.all(),
required=False,
source='user',
write_only=False)
class Meta:
model = Combination
depth = 1
fields = ('id', 'user_id', 'gameround_id', 'resource_id', 'tag_id', 'created', 'score')
def create(self, validated_data):
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
score = 0
combination = Combination(
user=user,
gameround=validated_data.get("gameround"),
resource=validated_data.get("resource"),
created=datetime.now(),
score=score
)
combination.save()
tag_data = validated_data.pop('tag_id', None)
if tag_data:
tag = Tag.objects.get_or_create(**tag_data)[0]
validated_data['tag_id'] = tag
combination.tag_id.add(validated_data.get("tag_id"))
return combination
This is the problematic code:
tag_data = validated_data.pop('tag_id', None)
if tag_data:
tag = Tag.objects.get_or_create(**tag_data)[0]
validated_data['tag_id'] = tag
combination.tag_id.add(validated_data.get("tag_id"))
This is the JSON object I am trying to send in Postman:
{
"gameround_id": 2015685170,
"resource_id": 327888,
"tag_id": [{"id": 2001372884, "name": "combination", "language": "en"}]
}
What am I doing wrong here? Can this be done any other way?
The tag_id maps on a list of dictionaries, not a dictionary. You thus can enumerate over it:
tag_data = validated_data.pop('tag_id', None)
for tag_item in tag_data:
tag = Tag.objects.get_or_create(**tag_item)[0]
validated_data['tag_id'] = tag
since these are lists, it might be better however to make a list of tags in the validated data:
tag_data = validated_data.pop('tag_id', None)
validated_data['tag_id'] = [
Tag.objects.get_or_create(**tag_item)[0]
for tag_item in tag_data
]

DRF getstream empty notifications

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?

How to load a dynamic saved dropdown during edit

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

django rest framework writable nested serializer returns empty nested data

I'm following this link (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers) to write nested serializer. But when I pop the 'vars' from validated_data in the create method of HostSerializer, I found it's empty.
I'm using django 1.9.2 and django restframework 3.3.2.
My model:
class Host(models.Model):
name = CharField(max_length=20, primary_key=True)
vm_cpu = IntegerField(default=2)
vm_mem = IntegerField(default=2048)
create_vm = BooleanField(default=True)
def __unicode__(self):
return('%s' % (self.name))
class Variable(models.Model):
name = CharField(max_length=10)
value = CharField(max_length=20)
host = models.ForeignKey(Host, related_name='vars')
def __unicode__(self):
return('%s=%s' % (self.name, self.value))
Serializer
class VariableSerializer(ModelSerializer):
class Meta:
model = Variable
class HostSerializer(ModelSerializer):
vars = VariableSerializer(many=True)
class Meta:
model = Host
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
host = Host.objects.create(**validated_data)
for v in vars_data:
Variable.objects.create(host = host, **v)
return host
This is the problem I found vars_data is an empty list:
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
Here's the rest of the code
admin.py
class VariableAdmin(admin.ModelAdmin):
list_display = ['name', 'value']
class HostAdmin(admin.ModelAdmin):
list_display = ['name']
admin.site.register(Variable, VariableAdmin)
admin.site.register(Host, HostAdmin)
urls.py
router = DefaultRouter()
router.register(r'variables', VariableViewSet, base_name='variables')
router.register(r'hosts', HostViewSet, base_name='hosts')
urlpatterns = [
url(r'^', include(router.urls)),
]
views.py
class VariableViewSet(ModelViewSet):
queryset = Variable.objects.all()
serializer_class = VariableSerializer
class HostViewSet(ModelViewSet):
queryset = Host.objects.all()
serializer_class = HostSerializer
My test program
post.py
import json
import requests
file = 'host.json'
url = 'http://localhost:8001/test_nest/hosts/'
with open(file, 'r') as f:
j = f.read()
data = json.loads(j)
r = requests.post(url, data = data)
print r.text
And here's the test data
host.json
{
"name": "host4",
"vars": [
{
"name": "var2-a",
"value": "a1"
},
{
"name": "var2-b",
"value": "a2"
}
],
"vm_cpu": 2,
"vm_mem": 2048,
"create_vm": true
}
I'm new to django. So I'm wondering if it's something simple and obvious. Did I use the wrong viewset? Did I post to the wrong URL? Or I setup the URL structure wrong?
I your serializers try using...
def update(self, instance,validated_data):
instance.vars_data = validated_data.get('vars',instance.vars)
instance.host = Host.objects.create(**validated_data)
for v in vars_data:
v,created=Variable.objects.create(host = host, **v)
instance.v.add(v)
return host
The following code works for me. Maybe you can try it:
models.py
class UserProfile(AbstractUser):
pass
class TobaccoCert(models.Model):
license = models.CharField(primary_key=True, max_length=15)
license_image = models.ImageField(upload_to="certs", blank=True, null=True, verbose_name='cert image')
views.py
class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = UserRegSerializer
...
def create(self, request, *args, **kwargs):
data = request.data
# here i make my nested data `certs` and update it to data
image_data = request.FILES.get('images')
_license = data.get('username', None)
certs = {'license': _license, 'license_image': image_data}
data['certs'] = certs
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer) # save it
ret_dict = serializer.data
payload = jwt_payload_handler(user)
ret_dict["token"] = jwt_encode_handler(payload) # 获取token
ret_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(ret_dict, status=status.HTTP_201_CREATED, headers=headers)
serializer.py
class CertSerializer(serializers.ModelSerializer):
pass
class UserRegSerializer(serializers.ModelSerializer):
# [Serializer relations - Django REST framework](https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers)
# this line is vars of your code
certs = CertSerializer(many=True,write_only=True)
...
password = serializers.CharField(
style={'input_type': 'password'}, help_text="passowrd", label="passowrd", write_only=True,
)
class Meta:
model = User
fields = ("username", "mobile", "password", 'province', 'city', 'dist', 'code', 'certs')
def create(self, validated_data):
"""
"""
# here we get certs
certs_data = self.initial_data.get('certs')
# just pop certs because it is useless for user create
certs_empty = validated_data.pop('certs')
user = super().create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
# use certs data to create cert
image_url = self.save_image(certs_data)
_license = validated_data.get('username', None)
TobaccoCert.objects.create(license=_license, license_image=image_url)
return user
In short, you need to add your nested parameters directly in the request parameters. In my example code, it is certs which is vars in your example and then use certs_data = self.initial_data.get('certs') get the parameters you passed in create method.
Another possible way is post your data before your requests:
In postman:
enter image description here
in request:
python - Django Rest Framework writable nested serializer with multiple nested objects - Stack Overflow
In addition, you can try to modify queryDict directly. You can refer to this link and here
Some useful links
django - Need help understanding many and source fields in a serializer - Stack Overflow
python - Django rest framework writeable nested serializer data missing from validated_data - Stack Overflow

Resources