I have a model for articles which takes a field FileField which is supposed to be a markdown file for the user to load their article already. I expose the api with a ModelViewSet.
This is saved to my media folder. I could fetch the content from the client side by GETing it from the href of course but that would mean 2 requests to my server:
get article info (title, content- this is the md, date published, description, etc.. ).
get content from the link.
But i'm wondering if there's a way to tell django to just send the content of the file instead of the href when it responds to a requestion for the article item.
Here's my model and api:
# ---------------------- #
# src/articles/models.py #
# ---------------------- #
from os.path import splitext
from uuid import uuid4
from django.db import models
# Create your models here.
def hashFilename(instance, name):
ext = splitext(name)[1]
return "articles/{}{}".format(uuid4(), ext)
def hashImageFilename(instance, name):
ext = splitext(name)[1]
return "images/{}{}".format(uuid4(), ext)
class Article(models.Model):
title = models.CharField(("title"), max_length=100)
content = models.FileField("content", upload_to=hashFilename)
description = models.TextField(("description"), default='')
uploadDate = models.DateTimeField(("uploadDate"), auto_now=True)
lastModified = models.DateTimeField(("uploadDate"), auto_now=True)
publicationDate = models.DateField("publicationDate")
image = models.ImageField("image", upload_to=hashImageFilename)
def __str__(self):
return self.title
# ------------------------- #
# src/articles/api/views.py #
# ------------------------- #
from rest_framework.viewsets import ModelViewSet
from ..models import Article
from .serializers import ArticleSerializerFull, ArticleSerializerShort
class ArticlesViewSet(ModelViewSet):
queryset = Article.objects.all()
def get_serializer_class(self):
if self.action == 'list':
serializer = ArticleSerializerShort
else:
serializer = ArticleSerializerFull
return serializer
queryset = Article.objects.all()
Defining a serializers.SerializerMethodField--(DRF Doc) method will do the job.
class ArticleSerializer(serializers.ModelSerializer):
content = serializers.SerializerMethodField()
def get_content(self, article):
return article.content.file.read()
class Meta:
fields = '__all__'
model = Article
Alternatively, you could achieve the same by overriding the to_representation method of the serializer.
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Article
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['content'] = instance.content.file.read()
return rep
Update-1
From this comment, I hope you need a live markdown editor in Django Admin rather than a FileField.
So, Use any of these markdown packages to get a live view in the Django Admin. These packages are using models.TextField to store the markdown content. So that you could read the content from the field in anywhere just like any other model fields
Related
I have a model.py in which I would like to upload multiple files. In my models I have main class and another which I'm trying to use to upload multiple file as suggested. Problem is, I get the following error in my class in views.py 'Exception Value: Unknown field(s) (file) specified for Post' and I can't get it to upload multiple files.
model.py
class Post(models.Model):
title = models.CharField(max_length=40, verbose_name="Naslov predmeta")
# a lot more field here but not important
file_1 = models.FileField(blank=True, upload_to='PN_datoteke/%Y/%m/%d/', verbose_name="Datoteka 1", validators=[validate_file_size])
file_2 = models.FileField(blank=True, upload_to='PN_datoteke/%Y/%m/%d/', verbose_name="Datoteka 2", validators=[validate_file_size])
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk':self.pk})
class File(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
file = models.FileField(blank=True, upload_to='PN_datoteke/%Y/%m/%d/', verbose_name="Datoteke", validators=[validate_file_size])
views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content_don', 'subject', 'rn_number', 'file_1', 'file_2']
def form_valid(self, form):
form.instance.author = self.request.user
#here I have a lot of code to process data which is writen in the fields
response = super(PostCreateView,self).form_valid(form)
#################################################################
##### this code below I'm trying to use to upload multiple files
obj = form.save(commit=False)
if self.request.FILES:
for f in self.request.FILES.getlist('file'):
obj = self.model.objects.create(file=f)
return response
I also tried using forms, but I can't get my scripts/querys and simmilar to work then. Is there a way to add file from class File in models to my views.py class and then use it to upload multiple fiels? Or is there a easier way?
forms.py
from django import forms
from .models import Post, File
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content_don', 'subject', 'rn_number', 'report_division', 'report_department', 'nc_type', 'number_res_people', 'res_person_1', 'res_person_2', 'res_person_3', 'res_person_4', 'urgency',
'date_days', 'cost_pn', 'cost_pn_tip', 'reklamacija', 'file_1', 'file_2', 'file_3', 'file_4']
class PostFileForm(PostForm): #extending form
file = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta(PostForm.Meta):
fields = PostForm.Meta.fields + ['file',]
html template has a script to add multiple files upload option to my field, I'm not using widgets in forms.
<script>
$(document).ready(function(){
$('#id_file_1').attr("multiple","true");
})
</script>
I couldn't find a solution to my problem and would appreciate comments/help on this.
I would like to develop a multiple user type model in Django, along the lines of this video where the author is using Django Proxy Models.
Situation
I have a list of XX projects (proj01, proj02 , projXX, ...).
All these projects have their specific page that can be accessed through a specific url mysite/projXX/
I have multiple users: Adam, Bob, Caroline, Dany, Ed, ...
Each user can have several roles according to the project they are working on (e.g. manager, developer, documentarist, reviewer, editor, ...)
A user can have a different role according to the project. E.g. Adam can be reviewer on proj01 but editor on proj02 while Bob can be editor on proj01 but reviewer on proj02, etc..
I started defining multiple user types in the models.py file below (only reviewer and editor roles):
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
class Types(models.TextChoices):
EDITOR= "EDITOR", "Editor"
REVIEWER = "REVIEWER", "Reviewer"
base_type = Types.EDITOR
type = models.CharField(
_("Type"), max_length=50, choices=Types.choices, default=base_type
)
name = models.CharField(_("Name of User"), blank=True, max_length=255)
def get_absolute_url(self):
return reverse("users:detail", kwargs={"username": self.username})
def save(self, *args, **kwargs):
if not self.id:
self.type = self.base_type
return super().save(*args, **kwargs)
class EditorManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(type=User.Types.EDITOR)
class ReviewerManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(type=User.Types.REVIEWER)
class EditorMore(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
gadgets = models.TextField()
class Editor(User):
base_type = User.Types.EDITOR
objects = EditorManager()
class Meta:
proxy = True
def edit(self):
return "Edition in progress"
class ReviewerMore(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
model = models.CharField(max_length=255)
make = models.CharField(max_length=255)
year = models.IntegerField()
class Reviewer(User):
base_type = User.Types.REVIEWER
objects = ReviewerManager()
#property
def more(self):
return self.reviewermore
class Meta:
proxy = True
def review(self):
return "In Review"
Question:
What is the best way to handle the fact that the role of the user can change according to the project page he/she is visiting?
Example: If Adam is logged in and visits the page mysite/proj01/ I would like him to access only the content allowed for a reviewer while if Adam visit mysite/proj02/, I would like the user to see only the content allowed to the editor.
Ideally, I would like each user to have its unique entry in the user database. I was thinking that the project-dependent role level could be stored as a dictionary? For example:
{'proj01':'reviewer', 'proj02':'editor', 'projxx': 'roleY', ... }
How would combine this user model and the list of project-dependent permissions?
Edit 02/07/21
Add example files for a project app, models.py and views.py :
# projects/models.py
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse
class Project(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("project_detail", args=[str(self.id)])
# projects/views.py
from django.contrib.auth.mixins import (
LoginRequiredMixin,
UserPassesTestMixin,
)
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, DeleteView, CreateView
from django.urls import reverse_lazy
from .models import Project
class ProjectListView(LoginRequiredMixin, ListView):
model = Project
template_name = "project_list.html"
class ProjectDetailView(LoginRequiredMixin, DetailView):
model = Article
template_name = "project_detail.html"
class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Project
fields = (
"title",
"body",
)
template_name = "project_edit.html"
def test_func(self):
obj = self.get_object()
return obj.author == self.request.user
If you have projects as a model. You can add custom permissions to the model. Then assign those permissions to your users appropriately for each project (actually easily add/remove permissions too).
Then use either user_passes_test or permissions_required in your views/template to restrict what users can see/access/edit.
class Project(models.Model):
project_name = ...
class RoleType(models.Model):
role_name = models.CharField
# Permission boolean flags
can_edit = models.Boolean
can_view = models.Boolean
class ProjectRole(models.Model):
project = models.ForeignKey('Project', ...)
role = models.ForeignKey('RoleType', ...)
user = models.ForeignKey('User', ...)
Now you can reverse lookup based on project or user
# To show all assigned users and their roles for a project
foo_project = Project.objects.get(project_name='foo')
project_roles = ProjectRole.objects.filter(project=foo_project)
You can also restrict your views and templates by roles and their permissions boolean flags.
Create groups that defines your Roles, for example : Group1: Editor, Group2:Manager and so on
Assign each user to the specified group ( you can do it in python manage.py shell or in admin panel )
Add restrictions on the view, for example: /mysite/projx/ view is restricted to groupA, you can check the following question that helps you with this point: https://stackoverflow.com/a/4789038/13508969
For example:
GroupA : GlobalEditor ( Bob can edit in projx and projy , and can only view projz )
GroupB : viewonly ( Adam can only view the content of the projs )
and so on
I'm trying to save an image from a url in the post request.
Here is what I have so far, the error is while passing the file to the serializer.
#models.py
class Picture(models.Model):
picture = models.ImageField(
upload_to=upload_location_picture, max_length=255
)
owner = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='owned_pictures')
#views.py
class PlanPostPictureByUrl(APIView):
'''
Class to post dislike to plan
'''
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
img_url = request.data["picture"]
name = urlparse(img_url).path.split('/')[-1]
response = requests.get(img_url)
if response.status_code == 200:
serializer = PlanPicturesSerializer(
data={"picture":ContentFile(response.content)})
if serializer.is_valid():
serializer.save(owner=self.request.user)
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
#serializers.py
class PlanPicturesSerializer(serializers.ModelSerializer):
class Meta:
model = Picture
fields = ['picture']
This is the error I am getting from the serializer:
{'picture': [ErrorDetail(string='No filename could be determined.', code='no_name')]}
I have something like this in my serializers create and update methods.
from django.core.files.uploadedfile import UploadedFile
url = validated_data['newIconFromURL']
# Remove url from the submitted data
validated_data['newIconFromURL'] = ''
# Download data from url (requires `requests` module. Can also be done with urllib)
response = requests.get(validated_data['newIconFromURL'])
# Set icon field (ImageField) to binary file
validated_data['icon'] = UploadedFile(BytesIO(response.content), name='icon')
Note that the file name does not need to be unique, as Django will append random characters to the actual filename to make it unique.
I am trying to use the django rest framework to carry out multiple POST events once I point it to a directory. The idea is that I will specify the country and a directory containing multiple images. Then the API must go to the directory, and post each image in that directory to the database along with more details available from the filename of each image. Following some pointers from the internet, I managed to build out a little model and framework that looks like it could work except for one thing. The default rest API page doesn't support the HTML input of lists.
I would like to know if my time will be better spent trying to fit the API page to my requirements or if I should go about building my own HTML interface.
Or to be more direct
What is the fastest (and simplest) way for me to upload multiple images (each image is a separate instance in the db) into a database using Django and the Django REST Framework?
models.py:
from django.db import models
# Create your models here.
class Tiles(models.Model):
image = models.ImageField(blank=True, null=True)
lat = models.FloatField(blank=True, null=True)
lng = models.FloatField(blank=True, null=True)
country = models.CharField(blank=True, null=True, max_length=10)
solar = models.FloatField(blank=True, null=True)
wind = models.FloatField(blank=True, null=True)
HV = models.FloatField(blank=True, null=True)
class Meta:
verbose_name_plural = "Tiles"
views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import FileListSerializer
from rest_framework import parsers
from .models import Tiles
# Create your views here.
def index(request):
return render(request, 'main/index.html')
class TileViewSet(viewsets.ModelViewSet):
serializer_class = FileListSerializer
parser_classes = (parsers.MultiPartParser, parsers.FormParser,)
queryset = Tiles.objects.all()
serializers.py
from rest_framework import serializers
from .models import Tiles
import mercantile
class FileListSerializer(serializers.Serializer):
image = serializers.ListField(
child=serializers.FileField(max_length=100000,
allow_empty_file=False,
use_url=False)
)
country = serializers.CharField(max_length=10)
# The create function below overrides the Django default create function to populate multiple fields using the image name
def create(self, validated_data):
country = validated_data.pop('country')
image = validated_data.pop('image')
for img in image:
path = img.path
path = path.split('/')[-1]
path = path.split('.')[0]
x, y, z = path.split('-')
coords = mercantile.ul(int(x), int(y), int(z))
tile = Tiles.objects.create(image=img, country=country, lat=coords.lat, lng=coords.lng, **validated_data)
return tile
class TileSerializer(serializers.ModelSerializer):
class Meta:
model = Tiles
fields = '__all__'
Screenshot of the resulting API page:
I've found a solution to this problem. Hope this helps someone down the line. In serializers.py I changed my ListField to a FilePathField that can take in directories as input.
New serializers.py:
from rest_framework import serializers
from .models import Tiles
import mercantile
import os
BASE_DIR = os.path.dirname((os.path.dirname(os.path.abspath(__file__))))
class FileListSerializer(serializers.Serializer):
# The following FilePathField is named image but refers to a directory containing multiple images
image = serializers.FilePathField(path=(os.path.join(BASE_DIR, 'opt_rec_master_img_dir')), allow_files=False, allow_folders=True)
country = serializers.CharField(max_length=10)
# The following variables for each instance are generated automatically upon clicking create
# They are included here so that the DRF auto generated json output includes this information
lat = serializers.FloatField(read_only=True)
lng = serializers.FloatField(read_only=True)
solar = serializers.FloatField(read_only=True)
wind = serializers.FloatField(read_only=True)
HV = serializers.FloatField(read_only=True)
# This function overrides the default create function to populate multiple fields given a directory full of images.
def create(self, validated_data):
country = validated_data.pop('country')
image = validated_data.pop('image')
directory = image.split('/')[-1]
for img in os.listdir(image):
path = img.split('.')[0]
x, y, z = path.split('-')
coords = mercantile.ul(int(x), int(y), int(z))
tile = Tiles.objects.create(image=os.path.join('opt_rec_master_img_dir', directory, img), country=country, lat=coords.lat, lng=coords.lng)
return tile
class Meta:
model = Tiles
views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import FileListSerializer
from .models import Tiles
from django.core.files.images import ImageFile
from rest_framework.utils.encoders import JSONEncoder
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
# Create your views here.
# def index(request):
# return render(request, 'main/index.html')
#
# JSONEncoder and JSONRenderer have been overriden here to serialize the image path rather than image byte data
class OREncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, ImageFile):
return obj.path
return super().default(obj)
class ORRenderer(JSONRenderer):
encoder_class = OREncoder
class TileViewSet(viewsets.ModelViewSet):
serializer_class = FileListSerializer
queryset = Tiles.objects.all()
renderer_classes = [ORRenderer, BrowsableAPIRenderer]
I want to ask about the use of ._meta in this code ? I didn't find a documentation that explains the use of .meta
def resend_activation_email(self, request, queryset):
"""
Re-sends activation emails for the selected users.
Note that this will *only* send activation emails for users
who are eligible to activate; emails will not be sent to users
whose activation keys have expired or who have already
activated.
"""
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
for profile in queryset:
if not profile.activation_key_expired():
profile.send_activation_email(site)
resend_activation_email.short_description = _("Re-send activation emails")
_meta as the name implies stores the meta data of the model. Say, you want to loop over all the fields of a model, then you can use model._meta.get_fields().
By the way, meta options are covered in the documentation -
Model Meta options documentation
You can also use ._meta in testing.
Consider the below models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
published = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.author} {self.title} {self.content}"
You could use ._meta to verify label names of a post (in test_models.py) like:
from datetime import datetime
from django.test import TestCase
from django.contrib.auth.models import User
from blog.models import Post
class TestBlogModels(TestCase):
def setUp(self):
author = User.objects.create(username='TestUser1')
self.post = Post.objects.create(
author=author,
title='Test Post Title 1',
content='Test content for title 1'
)
....
def test_blog_author_label(self):
author_field = Post._meta.get_field('author').verbose_name
self.assertEqual(author_field, "author")
def test_blog_title_label(self):
title_field = Post._meta.get_field('title').verbose_name
self.assertEqual(title_field, "title")
def test_blog_content_label(self):
content_field = Post._meta.get_field('content').verbose_name
self.assertEqual(content_field, "content")
def test_blog_title_max_length(self):
title_field_length = Post._meta.get_field('title').max_length
self.assertEqual(title_field_length, 100)
...
(verbose_name strips django.db.models.fields to the field speicified in models.py)