-
Notifications
You must be signed in to change notification settings - Fork 66
/
Copy pathserializers.py
132 lines (100 loc) · 4.03 KB
/
serializers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import logging
import re
import requests
from share import models
from django.core.files.base import ContentFile
from django.db import transaction
from rest_framework_json_api import serializers
from api.base import ShareSerializer
from api.base import exceptions
from api.fields import ShareIdentityField
from api.users.serializers import ShareUserSerializer
from api.users.serializers import ShareUserWithTokenSerializer
logger = logging.getLogger(__name__)
class ReadonlySourceSerializer(ShareSerializer):
# link to self
url = ShareIdentityField(view_name='api:source-detail')
class Meta:
model = models.Source
fields = (
'name',
'home_page',
'long_title',
'icon',
'url',
'source_configs',
)
read_only_fields = fields
class UpdateSourceSerializer(ShareSerializer):
VALID_ICON_TYPES = ('image/png', 'image/jpeg')
included_serializers = {
'user': ShareUserWithTokenSerializer,
}
# link to self
url = ShareIdentityField(view_name='api:source-detail')
# URL to fetch the source's icon
icon_url = serializers.URLField(write_only=True)
class Meta:
model = models.Source
fields = ('name', 'home_page', 'long_title', 'canonical', 'icon', 'icon_url', 'user', 'url')
read_only_fields = ('icon', 'user', 'url')
view_name = 'api:source-detail'
class JSONAPIMeta:
included_resources = ['user']
def update(self, instance, validated_data):
# TODO: when long_title is changed, reindex works accordingly
icon_url = validated_data.pop('icon_url', None)
with transaction.atomic():
instance = super().update(instance, validated_data)
if icon_url:
icon_file = self._fetch_icon_file(icon_url)
instance.icon.save(instance.name, content=icon_file)
return instance
def _fetch_icon_file(self, icon_url):
try:
r = requests.get(icon_url, timeout=5)
header_type = r.headers['content-type'].split(';')[0].lower()
if header_type not in self.VALID_ICON_TYPES:
raise serializers.ValidationError('Invalid image type.')
return ContentFile(r.content)
except Exception as e:
logger.warning('Exception occured while downloading icon %s', e)
raise serializers.ValidationError('Could not download/process image.')
class CreateSourceSerializer(UpdateSourceSerializer):
# Don't use validators to enforce uniqueness, so we can return the conflicting object
class Meta(UpdateSourceSerializer.Meta):
extra_kwargs = {
'name': {'required': False, 'validators': []},
'long_title': {'validators': []},
}
def create(self, validated_data):
icon_url = validated_data.pop('icon_url')
long_title = validated_data.pop('long_title')
icon_file = self._fetch_icon_file(icon_url)
username = re.sub(r'[^\w.@+-]', '_', long_title).lower()
name = validated_data.pop('name', username)
with transaction.atomic():
source, created = models.Source.objects.get_or_create(
long_title=long_title,
defaults={
'name': name,
'canonical': True,
**validated_data
}
)
if not created:
raise exceptions.AlreadyExistsError(source)
user = self._create_trusted_user(username=username)
source.user_id = user.id
source.icon.save(name, content=icon_file)
return source
def _create_trusted_user(self, username):
user_serializer = ShareUserSerializer(
data={'username': username, 'is_trusted': True},
context={'request': self.context['request']}
)
user_serializer.is_valid(raise_exception=True)
user = user_serializer.save()
user.set_unusable_password()
user.save()
return user