Validation in Modelviewset - python

I have to validate image. I have tried add validator to field in models.py, but python cannot migrate validator depending on class. How can I add validation in this case in views.py
validators.py
class ValidateImageSize:
MEASURE_UNIT_BYTE_MAP = {
'kb': 1024,
'mb': 1024 * 1024,
}
MEASURE_UNIT_NAME_MAP = {
'kb': 'kilobyte',
'mb': 'megabyte',
}
def __init__(self, max_size):
max_size, measure_unit = max_size.split(' ')
self.max_size = int(max_size)
self.measure_unit = measure_unit
def __call__(self, value):
if value.size > self.max_size * self.MEASURE_UNIT_BYTE_MAP[self.measure_unit]:
raise ValidationError(f'Size maximum is {self.max_size} {self.measure_unit}')
views.py
class EpisodeView(viewsets.ModelViewSet):
def create(self, request, story_id=None, *args, **kwargs):
try:
story = Story.objects.get(pk=story_id)
except Story.DoesNotExist:
raise NotFound
kwargs = {
'story': story,
'title': request.data.get('title'),
'cover': request.data.get('cover'),
}
episode = Episode.objects.create(**kwargs)
for image in dict(request.data.lists())['images']:
EpisodeImage.objects.create(episode=episode, image=image)
return Response({'episode_id': episode.id}, status=201)

At first we are going to validate all images is properly size/formatted or not and if there is no error then we will do create operation.
from .validators import ValidateImageSize
class EpisodeView(viewsets.ModelViewSet):
def create(self, request, story_id=None, *args, **kwargs):
try:
story = Story.objects.get(pk=story_id)
except Story.DoesNotExist:
raise NotFound
# now we will run image validation
custom_image_validator = ValidateImageSize() # with your desize size
for image in dict(request.data.lists())['images']:
custom_image_validator(image) # this will raise validation error if any validation error occur
# After that will do our regular work
kwargs = {
'story': story,
'title': request.data.get('title'),
'cover': request.data.get('cover'),
}
episode = Episode.objects.create(**kwargs)
for image in dict(request.data.lists())['images']:
EpisodeImage.objects.create(episode=episode, image=image)
return Response({'episode_id': episode.id}, status=201)

Related

'dict' object has no attribute '_committed' can anyone explain why this error occurs

I am sending files from the frontend and I want to save file location in database I am using DRF.
Currently, I am trying to save only one file but i am getting error
"'dict' object has no attribute '_committed'"
Can Anyone please give me some suggestions on what should I do?
Request Body I am getting for on file:-
{'id': "Please provide Site Contact person's Name", 'service': {'id': 3, 'service_name': 'Site
Survey', 'cost_per_kw': 1, 'min_size': 101, 'upto_size': 10000, 'tat': 5, 'creation_date':
None}, 'question': 3, 'creation_date': None, 'real_estate': 0, 'answer': 'VIkrant Patil',
'attachement': [{'name': 'Vikrant Patil - Offer Letter (1).pdf', 'size': 172518, 'url':
'blob:http://localhost:8000/cad9de5d-a12b-41a2-90f7-38deff67fd0f', '_file': {}}], 'project':
189}
My view.py logic for saving only one file:-
class ServiceProjectAnswerViewSet(viewsets.ModelViewSet):
model = ServiceProjectAnswer
serializer_class = ServiceProjectAnswerSerializer
# parser_classes = (MultiPartParser, FormParser)
def create(self, request, *args, **kwargs):
print(request.data)
instance = None
answers = request.data.copy()
data = {}
attachements = answers['attachement']
print(attachements[0])
data['answer'] = answers['answer']
data['project'] = answers['project']
data['question'] = answers['question']
serializer = self.get_serializer(data=data)
if serializer.is_valid(raise_exception=True):
try:
instance = serializer.save()
# for attachement in attachements:
AnswerAttachments.objects.create(
answer = instance,
# attachement = attachement
attachement = attachements[0]
)
except Exception as e:
print(e)
return Response(str(e), status=400)
return Response(serializer.data, status=201)
My view.py logic for saving only multiple files :-
In my responce when I a sending multiple files I am sending list of Objects every object has values which i want that is why I have use for loop to iterate.
def create(self, request, *args, **kwargs):
instance = None
print(request.data)
answers = request.data.copy()
for answer in answers:
data = {}
attachements = answer['attachement']
print(attachements,"attachement")
data['answer'] = answer['answer']
data['project'] = answer['project']
data['question'] = answer['question']
print(data,"data")
serializer = self.get_serializer(data=data)
if serializer.is_valid(raise_exception=True):
try:
instance=serializer.save()
for attachement in attachements:
print(attachement)
# attach = {}
# attach['name'] = attachement['name']
# attach['size'] = attachement['size']
# attach['url'] = attachement['url']
print(type(attachement))
AnswerAttachments.objects.create(
answer=instance,
attachement=attachement
)
except Exception as e:
print(e)
return Response(str(e), status=400)
else:
return Response({'message': ('Failed to save answers')}, status=400)
return Response('Answers are saved successfully!!', status=200)
Try something like this,
const formData = new FormData();
formData.append('file', file);
const requestOptions = {
method: "POST",
body:formData
};
Means,try to send body of request in multiform instead of application/json.

ValueError at /admin/marketing/marketingpreference/add/, too many values to unpack (expected 2)

I'm try to my User model email in Marketing app. I was following the marketing app tutorial from video. but in that video that program doesn't show error. when i was write same program it's show error:
ValueError at /admin/marketing/marketingpreference/add/
too many values to unpack (expected 2)
from django.db import models
from django.db.models.signals import post_save, pre_save
from .utils import Mailchimp
from accounts.models import User
class MarketingPreference(models.Model):
user =models.OneToOneField(User, on_delete=models.CASCADE)
subscribed = models.BooleanField(default=True)
mailchimp_subscribed = models.NullBooleanField(blank=True)
mailchimp_msg= models.TextField(null=True, blank=True)
timestamp =models.DateTimeField(auto_now_add=True)
update =models.DateTimeField(auto_now= True)
def __str__(self):
return self.user.email
def marketing_pref_update_receiver(sender, instance, *args, **kwargs):
if instance.subscribed != instance.mailchimp_subscribed:
if instance.subscribed:
status_code, response_data = Mailchimp().unsubscribe(instance.user.email)
else:
status_code, response_data = Mailchimp().subscribe(instance.user.email)
if response_data['status'] == 'subscribed' :
instance.subscribed = True
instance.mailchimp_subscribed = True
instance.mailchimp_msg = response_data
else:
instance.subscribed = False
instance.mailchimp_subscribed = False
instance.mailchimp_msg = response_data
pre_save.connect(marketing_pref_update_receiver, sender=MarketingPreference)
def make_marketing_pref_receiver(sender, instance, created, *args, **kwargs):
if created:
MarketingPreference.objects.get_or_create(user=instance)
post_save.connect(make_marketing_pref_receiver, sender=User)
and my utils.py
class Mailchimp(object):
def __init__(self):
super(Mailchimp, self).__init__()
self.key = MAILCHIMP_API_KEY
self.api_url = "https://{dc}.api.mailchimp.com/3.0".format(dc=MAILCHIMP_DATA_CENTER)
self.list_id = MAILCHIMP_EMAIL_LIST_ID
self.list_endpoint = '{api_url}/lists/{list_id}'.format(api_url=self.api_url, list_id=self.list_id)
def get_member_endpoint(self):
return self.list_endpoint + "/members"
def check_valid_status(self, status):
choices = ['subscribed','unsubscribed','cleaned', 'pending']
if status not in choices:
raise ValueError("Not a valid choice for email status")
return status
def add_email(self, email,status = "subscribed"):
self.check_valid_status(status)
data = {
"email_address": email,
"status": status
}
endpoint = self.get_member_endpoint()
r =requests.post(endpoint, auth=("", self.key), data=json.dumps(data))
return r.json()
def subscribe(self, email):
return self.add_email(email)
and my terminal showing this
[14/Feb/2020 21:15:38] "GET /admin/jsi18n/ HTTP/1.1" 200 3223
Internal Server Error: /admin/marketing/marketingpreference/add/
Traceback (most recent call last):
"D:\software\shopw\marketing\models.py", line 35, in marketing_pref_update_receiver
status_code, response_data = Mailchimp().unsubscribe(instance.user.email)
ValueError: too many values to unpack (expected 2)
please help me.

ElasticSearch : Index a document in my django web application

I'm trying to use for the first time ElasticSearch in my web application and I have difficulties with my ES indexation.
The process is :
When I add a document with upload field, the document is indexed by ES
I have a function which let to define a new document title
I have to reindex this document with new title in order to appear in ES list of documents
I have some issues with this last part and I would like to know if you could help me.
Actual processus :
models.py file :
class Document(EdqmFullTable):
CAT_CHOICES = (...)
...
file = models.FileField(upload_to=upload_file)
def get_filename(self):
return os.path.join(settings.MEDIA_ROOT, str(self.file))
When a new document is added through this model, it calls ES methods :
es4omcl.py file :
class EdqmES(object):
host = 'localhost'
port = 9200
es = None
def __init__(self, *args, **kwargs):
self.host = kwargs.pop('host', self.host)
self.port = kwargs.pop('port', self.port)
# Connect to ElasticSearch server
self.es = Elasticsearch([{
'host': self.host,
'port': self.port
}])
def __str__(self):
return self.host + ':' + self.port
#staticmethod
def file_encode(filename):
with open(filename, "rb") as f:
return b64encode(f.read()).decode('utf-8')
def create_pipeline(self):
body = {
"description": "Extract attachment information",
"processors": [
{"attachment": {
"field": "data",
"target_field": "attachment",
"indexed_chars": -1
}},
{"remove": {"field": "data"}}
]
}
self.es.index(
index='_ingest',
doc_type='pipeline',
id='attachment',
body=body
)
def index_document(self, doc, bulk=False):
filename = doc.get_filename()
try:
data = self.file_encode(filename)
except IOError:
data = ''
print('ERROR with ' + filename)
# TODO: log error
item_body = {
'_id': doc.id,
'data': data,
'relative_path': str(doc.file),
'title': doc.title,
}
if bulk:
return item_body
result1 = self.es.index(
index='omcl', doc_type='annual-report',
id=doc.id,
pipeline='attachment',
body=item_body,
request_timeout=60
)
print(result1)
return result1
And signals from callback.py file when a new document is saved into the database :
#receiver(signals.post_save, sender=Document, dispatch_uid='add_new_doc')
def add_document_handler(sender, instance=None, created=False, **kwargs):
""" When a document is created index new annual report (only) with Elasticsearch and update conformity date if the
document is a new declaration of conformity
:param sender: Class which is concerned
:type sender: the model class
:param instance: Object which was just saved
:type instance: model instance
:param created: True for a creation, False for an update
:type created: boolean
:param kwargs: Additional parameter of the signal
:type kwargs: dict
"""
if not created:
return
# Update Conformity declaration date
if instance.category == Document.OPT_CD:
now = datetime.today()
Omcl.objects.filter(id=instance.omcl_id).update(last_conformity=now)
# Index only annual reports
elif instance.category == Document.OPT_ANNUAL:
es = EdqmES()
es.index_document(instance)
My process :
I defined a new class which let to handle document as soon as they are uploaded. I can modify the document title. So the last step is : reindex this modified document with ES.
class ManageDocView(AdminRequiredMixin, View, BaseException):
""" Render the Admin Manage documents to update year in the filename"""
template_name = 'omcl/manage_doc_form.html'
form_class = ManageDocForm
success_url = 'omcl/manage_doc_form.html'
def get(self, request):
form = self.form_class()
context = {
"form": form
}
return render(request, self.template_name, context)
def post(self, request):
form = self.form_class()
query_document_updated = None
query_omcl = None
query_document = None
if "SearchOMCL" in request.POST:
omcl_list = request.POST['omcl_list']
query_omcl = Omcl.objects.get(id=omcl_list)
query_document = Document.objects.filter(omcl=omcl_list)
elif "UpdateDocument" in request.POST:
checkbox_id = request.POST['DocumentChoice']
checkbox_id_minus_1 = int(checkbox_id) - 1
query_document_updated = Document.objects.get(id=checkbox_id)
print(query_document_updated.id)
omclcode = query_document_updated.omcl.code
src_filename = query_document_updated.src_filename
filename, file_extension = os.path.splitext(src_filename)
category = query_document_updated.category
if category == "ANNUAL":
category = "ANNUAL_REPORT"
year = self.request.POST.get('q1year')
# Create the new document title updated by the new year
new_document_title = f"{year}_{category}_{omclcode}_{checkbox_id_minus_1} - {src_filename}"
# Create the new document file updated by the new year
new_document_file = f"omcl_docs/{omclcode}/{year}_{category}_{omclcode}_{checkbox_id_minus_1}{file_extension}"
# Get file.name in order to rename document file in /media/
document_path = query_document_updated.file.name
try:
actual_document_path = os.path.join(settings.MEDIA_ROOT, document_path)
new_document_path_temp = settings.MEDIA_ROOT + "/" + new_document_file
new_document_path = os.rename(actual_document_path, new_document_path_temp)
except FileNotFoundError:
messages.error(self.request, _(f"Document {src_filename} doesn't exist in the server"))
return redirect('manage_doc')
else:
# Assign modifications to selected document and save it into the database
query_document_updated.title = new_document_title
query_document_updated.file = new_document_file
query_document_updated.save()
messages.success(self.request, _(f"The modification has been taken account"))
context = {
'form': form,
'query_omcl': query_omcl,
'query_document': query_document,
'query_document_updated': query_document_updated,
}
return render(request, self.template_name, context)
I'm completely list because I don't know How I can adapt this part in callback.py file :
if not created:
return
with my Django ManageDocView() class

Test POST method to Django REST viewsets

Docs says only mapping of GET
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
tests.py:
def test_admin_can_create_role(userprofiles, aoo_admin, bug_manager, note_admin):
aoo = User.objects.get(username='aoo')
factory = APIRequestFactory()
view = RoleViewSet.as_view()
url = reverse('api:role-list')
data = {
'name': 'FirstAdmin',
'type': Role.RoleType.admin,
'company': 1,
}
request = factory.post(url, data=data, format='json')
force_authenticate(request, user=aoo)
response = view(request)
assert 201 == response.data
viewsets.py
class RoleViewSetPermission(permissions.BasePermission):
message = 'Only Manager or Admin are allowed'
def has_permission(self, request, view):
user = request.user
return user.has_perm('roles.add_role') \
and user.has_perm('roles.change_role') \
and user.has_perm('roles.delete_role')
class RoleViewSet(viewsets.ModelViewSet):
permission_classes = (RoleViewSetPermission,)
queryset = Role.objects.all()
serializer_class = RoleSerializer
filter_backends = (filters.DjangoFilterBackend, SearchFilter)
filter_class = RoleFilter
search_fields = ('name', 'description', 'user__username', 'company__name', 'company__name_th')
def filter_queryset(self, queryset):
try:
company = self.request.user.user_profile.companyappid.company
except AttributeError:
logger.error(f'{self.request.user} has AttributeError')
return Role.objects.none()
else:
logger.info(f'{self.request.user} is {company} member')
return queryset.filter(company=company)
Trackback:
cls = <class 'poinkbackend.apps.roles.api.viewsets.RoleViewSet'>, actions = None, initkwargs = {}
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# The suffix initkwarg is reserved for identifying the viewset type
# eg. 'List' or 'Instance'.
cls.suffix = None
# actions must not be empty
if not actions:
> raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
E TypeError: The `actions` argument must be provided when calling `.as_view()` on a ViewSet. For example `.as_view({'get': 'list'})`
../../.pyenv/versions/3.6.3/envs/poink/lib/python3.6/site-packages/rest_framework/viewsets.py:55: TypeError
Question:
How to do force_authenticate and request.post to the viewsets?
I have no problem with get. It has an answer already in the SO
References:
http://www.django-rest-framework.org/api-guide/viewsets/
I have to use APIClient not APIRequestFactory.
I though it has only one way to do testing.
Here is my example.
def test_admin_can_create_role(userprofiles, aoo_admin, bug_manager, note_admin):
aoo = User.objects.get(username='aoo')
client = APIClient()
client.force_authenticate(user=aoo)
url = reverse('api:role-list')
singh = Company.objects.get(name='Singh')
data = {
'name': 'HairCut',
'type': Role.RoleType.admin,
'company': singh.id, # Must be his companyid. Reason is in the RoleSerializer docstring
}
response = client.post(url, data, format='json')
assert 201 == response.status_code

Time input format in AdminTimeWidget in django

I use admin calendar and time widgets in django form: description, how to use them
But widget input time format is: "%H:%M:%S". I need "%H:%M". Here is my code:
class CorporateOrderForm(ModelForm):
class Meta:
model=Order
#exclude=('giving_address_comment','giving_address','comment')
def __init__(self, *args, **kwargs):
super(CorporateOrderForm, self).__init__(*args, **kwargs)
self.fields['order_date'].widget = widgets.AdminDateWidget()
self.fields['order_time'].widget = widgets.AdminTimeWidget(format='%H:%M')
Last row is important: I try to change format. Here is AdminTimeWidget code:
class AdminTimeWidget(forms.TimeInput):
class Media:
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
def __init__(self, attrs={}, format=None):
super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}, format=format)
And here is TimeInput code:
class TimeInput(Input):
input_type = 'text'
format = '%H:%M:%S' # '14:30:59'
def __init__(self, attrs=None, format=None):
super(TimeInput, self).__init__(attrs)
if format:
self.format = format
self.manual_format = True
else:
self.format = formats.get_format('TIME_INPUT_FORMATS')[0]
self.manual_format = False
It's not work, time format not changed. Where is the error?...
Do it this way:
class CorporateOrderForm(ModelForm):
class Meta:
model=Order
widgets = {
'order_date': widgets.AdminDateWidget(),
'order_time': widgets.AdminTimeWidget(format='%H:%M')
}

Categories

Resources