标签归档:angularjs

Django Rest Framework文件上传

问题:Django Rest Framework文件上传

我正在使用Django Rest Framework和AngularJs上传文件。我的视图文件如下所示:

class ProductList(APIView):
    authentication_classes = (authentication.TokenAuthentication,)
    def get(self,request):
        if request.user.is_authenticated(): 
            userCompanyId = request.user.get_profile().companyId
            products = Product.objects.filter(company = userCompanyId)
            serializer = ProductSerializer(products,many=True)
            return Response(serializer.data)

    def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(data=request.DATA)

由于post方法的最后一行应返回所有数据,因此我有几个问题:

  • 如何检查里面是否有东西request.FILES
  • 如何序列化文件字段?
  • 我应该如何使用解析器?

I am using Django Rest Framework and AngularJs to upload a file. My view file looks like this:

class ProductList(APIView):
    authentication_classes = (authentication.TokenAuthentication,)
    def get(self,request):
        if request.user.is_authenticated(): 
            userCompanyId = request.user.get_profile().companyId
            products = Product.objects.filter(company = userCompanyId)
            serializer = ProductSerializer(products,many=True)
            return Response(serializer.data)

    def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(data=request.DATA)

As the last line of post method should return all the data, I have several questions:

  • how to check if there is anything in request.FILES?
  • how to serialize file field?
  • how should I use parser?

回答 0

使用FileUploadParser,一切都在请求中。改用put方法,您会在docs中找到一个示例:)

class FileUploadView(views.APIView):
    parser_classes = (FileUploadParser,)

    def put(self, request, filename, format=None):
        file_obj = request.FILES['file']
        # do some stuff with uploaded file
        return Response(status=204)

Use the FileUploadParser, it’s all in the request. Use a put method instead, you’ll find an example in the docs :)

class FileUploadView(views.APIView):
    parser_classes = (FileUploadParser,)

    def put(self, request, filename, format=None):
        file_obj = request.FILES['file']
        # do some stuff with uploaded file
        return Response(status=204)

回答 1

我使用的是同一堆栈,也在寻找文件上传的示例,但是由于我使用ModelViewSet而不是APIView,因此情况更简单。原来的钥匙是pre_save钩子。我最终将其与angular-file-upload模块一起使用,如下所示:

# Django
class ExperimentViewSet(ModelViewSet):
    queryset = Experiment.objects.all()
    serializer_class = ExperimentSerializer

    def pre_save(self, obj):
        obj.samplesheet = self.request.FILES.get('file')

class Experiment(Model):
    notes = TextField(blank=True)
    samplesheet = FileField(blank=True, default='')
    user = ForeignKey(User, related_name='experiments')

class ExperimentSerializer(ModelSerializer):
    class Meta:
        model = Experiment
        fields = ('id', 'notes', 'samplesheet', 'user')

// AngularJS
controller('UploadExperimentCtrl', function($scope, $upload) {
    $scope.submit = function(files, exp) {
        $upload.upload({
            url: '/api/experiments/' + exp.id + '/',
            method: 'PUT',
            data: {user: exp.user.id},
            file: files[0]
        });
    };
});

I’m using the same stack and was also looking for an example of file upload, but my case is simpler since I use the ModelViewSet instead of APIView. The key turned out to be the pre_save hook. I ended up using it together with the angular-file-upload module like so:

# Django
class ExperimentViewSet(ModelViewSet):
    queryset = Experiment.objects.all()
    serializer_class = ExperimentSerializer

    def pre_save(self, obj):
        obj.samplesheet = self.request.FILES.get('file')

class Experiment(Model):
    notes = TextField(blank=True)
    samplesheet = FileField(blank=True, default='')
    user = ForeignKey(User, related_name='experiments')

class ExperimentSerializer(ModelSerializer):
    class Meta:
        model = Experiment
        fields = ('id', 'notes', 'samplesheet', 'user')

// AngularJS
controller('UploadExperimentCtrl', function($scope, $upload) {
    $scope.submit = function(files, exp) {
        $upload.upload({
            url: '/api/experiments/' + exp.id + '/',
            method: 'PUT',
            data: {user: exp.user.id},
            file: files[0]
        });
    };
});

回答 2

最后,我可以使用Django上传图片。这是我的工作代码

views.py

class FileUploadView(APIView):
    parser_classes = (FileUploadParser, )

    def post(self, request, format='jpg'):
        up_file = request.FILES['file']
        destination = open('/Users/Username/' + up_file.name, 'wb+')
        for chunk in up_file.chunks():
            destination.write(chunk)
        destination.close()  # File should be closed only after all chuns are added

        # ...
        # do some stuff with uploaded file
        # ...
        return Response(up_file.name, status.HTTP_201_CREATED)

urls.py

urlpatterns = patterns('', 
url(r'^imageUpload', views.FileUploadView.as_view())

卷曲请求上传

curl -X POST -S -H -u "admin:password" -F "file=@img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload

Finally I am able to upload image using Django. Here is my working code

views.py

class FileUploadView(APIView):
    parser_classes = (FileUploadParser, )

    def post(self, request, format='jpg'):
        up_file = request.FILES['file']
        destination = open('/Users/Username/' + up_file.name, 'wb+')
        for chunk in up_file.chunks():
            destination.write(chunk)
        destination.close()  # File should be closed only after all chuns are added

        # ...
        # do some stuff with uploaded file
        # ...
        return Response(up_file.name, status.HTTP_201_CREATED)

urls.py

urlpatterns = patterns('', 
url(r'^imageUpload', views.FileUploadView.as_view())

curl request to upload

curl -X POST -S -H -u "admin:password" -F "file=@img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload

回答 3

在此上花了1天后,我发现…

对于需要上载文件并发送一些数据的人,没有直接的前进方式可以使它工作。json api规范中有一个未解决的问题。一种可能性是我见过在使用multipart/related这里,但是我认为很难在drf中实现它。

最后,我实现的是将请求发送为formdata。您将每个文件作为文件发送,所有其他数据作为文本发送。现在,以文本形式发送数据有两种选择。情况1)您可以将每个数据作为键值对发送,或情况2)您可以有一个名为data的键,并将整个json作为值字符串发送。

如果您具有简单的字段,则第一种方法开箱即用,但如果嵌套了序列化,则将是一个问题。多部分解析器将无法解析嵌套字段。

下面我提供两种情况的实现

型号

class Posts(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    caption = models.TextField(max_length=1000)
    media = models.ImageField(blank=True, default="", upload_to="posts/")
    tags = models.ManyToManyField('Tags', related_name='posts')

serializers.py->无需特殊更改,由于可写的ManyToMany Field含义,此处没有显示我的序列化程序太长。

views.py

class PostsViewset(viewsets.ModelViewSet):
    serializer_class = PostsSerializer
    #parser_classes = (MultipartJsonParser, parsers.JSONParser) use this if you have simple key value pair as data with no nested serializers
    #parser_classes = (parsers.MultipartParser, parsers.JSONParser) use this if you want to parse json in the key value pair data sent
    queryset = Posts.objects.all()
    lookup_field = 'id'

现在,如果您遵循第一种方法,并且仅将非Json数据作为键值对发送,则不需要自定义解析器类。DRF的MultipartParser将完成这项工作。但是对于第二种情况,或者如果您有嵌套的序列化器(如我所示),则需要自定义解析器,如下所示。

utils.py

from django.http import QueryDict
import json
from rest_framework import parsers

class MultipartJsonParser(parsers.MultiPartParser):

    def parse(self, stream, media_type=None, parser_context=None):
        result = super().parse(
            stream,
            media_type=media_type,
            parser_context=parser_context
        )
        data = {}

        # for case1 with nested serializers
        # parse each field with json
        for key, value in result.data.items():
            if type(value) != str:
                data[key] = value
                continue
            if '{' in value or "[" in value:
                try:
                    data[key] = json.loads(value)
                except ValueError:
                    data[key] = value
            else:
                data[key] = value

        # for case 2
        # find the data field and parse it
        data = json.loads(result.data["data"])

        qdict = QueryDict('', mutable=True)
        qdict.update(data)
        return parsers.DataAndFiles(qdict, result.files)

该序列化程序将基本上解析值中的所有json内容。

邮递员在两种情况下的请求示例:情况1

情况二

After spending 1 day on this, I figured out that …

For someone who needs to upload a file and send some data, there is no straight fwd way you can get it to work. There is an open issue in json api specs for this. One possibility i have seen is to use multipart/related as shown here, but i think its very hard to implement it in drf.

Finally what i had implemented was to send the request as formdata. You would send each file as file and all other data as text. Now for sending the data as text you have two choices. case 1) you can send each data as key value pair or case 2) you can have a single key called data and send the whole json as string in value.

The first method would work out of the box if you have simple fields, but will be a issue if you have nested serializes. The multipart parser wont be able to parse the nested fields.

Below i am providing the implementation for both the cases

Models.py

class Posts(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    caption = models.TextField(max_length=1000)
    media = models.ImageField(blank=True, default="", upload_to="posts/")
    tags = models.ManyToManyField('Tags', related_name='posts')

serializers.py -> no special changes needed, not showing my serializer here as its too lengthy because of the writable ManyToMany Field implimentation.

views.py

class PostsViewset(viewsets.ModelViewSet):
    serializer_class = PostsSerializer
    #parser_classes = (MultipartJsonParser, parsers.JSONParser) use this if you have simple key value pair as data with no nested serializers
    #parser_classes = (parsers.MultipartParser, parsers.JSONParser) use this if you want to parse json in the key value pair data sent
    queryset = Posts.objects.all()
    lookup_field = 'id'

Now, if you are following the first method and is only sending non-Json data as key value pairs, you don’t need a custom parser class. DRF’d MultipartParser will do the job. But for the second case or if you have nested serializers (like i have shown) you will need custom parser as shown below.

utils.py

from django.http import QueryDict
import json
from rest_framework import parsers

class MultipartJsonParser(parsers.MultiPartParser):

    def parse(self, stream, media_type=None, parser_context=None):
        result = super().parse(
            stream,
            media_type=media_type,
            parser_context=parser_context
        )
        data = {}

        # for case1 with nested serializers
        # parse each field with json
        for key, value in result.data.items():
            if type(value) != str:
                data[key] = value
                continue
            if '{' in value or "[" in value:
                try:
                    data[key] = json.loads(value)
                except ValueError:
                    data[key] = value
            else:
                data[key] = value

        # for case 2
        # find the data field and parse it
        data = json.loads(result.data["data"])

        qdict = QueryDict('', mutable=True)
        qdict.update(data)
        return parsers.DataAndFiles(qdict, result.files)

This serializer would basically parse any json content in the values.

The request example in post man for both cases: case 1 ,

Case 2


回答 4

我用ModelViewSet和ModelSerializer解决了这个问题。希望这对社区有所帮助。

我还希望在序列化程序本身而不是视图中进行验证和Object-> JSON(反之亦然)登录。

让我们通过示例来了解它。

说,我想创建FileUploader API。它将在其中存储ID,file_path,file_name,大小,所有者等字段的位置。请参阅下面的示例模型:

class FileUploader(models.Model):
    file = models.FileField()
    name = models.CharField(max_length=100) #name is filename without extension
    version = models.IntegerField(default=0)
    upload_date = models.DateTimeField(auto_now=True, db_index=True)
    owner = models.ForeignKey('auth.User', related_name='uploaded_files')
    size = models.IntegerField(default=0)

现在,对于API,这就是我想要的:

  1. 得到:

当我触发GET端点时,我希望每个上传文件都具有以上所有字段。

  1. 开机自检:

但是对于用户创建/上传文件而言,为什么她不得不担心传递所有这些字段。她可以仅上传文件,然后,我想,序列化程序可以从上传的FILE中获取其余字段。

Searilizer: 问题:我在序列化器下面创建了我的目的。但是不确定它是否是实现它的正确方法。

class FileUploaderSerializer(serializers.ModelSerializer):
    # overwrite = serializers.BooleanField()
    class Meta:
        model = FileUploader
        fields = ('file','name','version','upload_date', 'size')
        read_only_fields = ('name','version','owner','upload_date', 'size')

   def validate(self, validated_data):
        validated_data['owner'] = self.context['request'].user
        validated_data['name'] = os.path.splitext(validated_data['file'].name)[0]
        validated_data['size'] = validated_data['file'].size
        #other validation logic
        return validated_data

    def create(self, validated_data):
        return FileUploader.objects.create(**validated_data)

参考视图集:

class FileUploaderViewSet(viewsets.ModelViewSet):
    serializer_class = FileUploaderSerializer
    parser_classes = (MultiPartParser, FormParser,)

    # overriding default query set
    queryset = LayerFile.objects.all()

    def get_queryset(self, *args, **kwargs):
        qs = super(FileUploaderViewSet, self).get_queryset(*args, **kwargs)
        qs = qs.filter(owner=self.request.user)
        return qs

I solved this problem with ModelViewSet and ModelSerializer. Hope this will help community.

I also preffer to have validation and Object->JSON (and vice-versa) login in serializer itself rather than in views.

Lets understand it by example.

Say, I want to create FileUploader API. Where it will be storing fields like id, file_path, file_name, size, owner etc in database. See sample model below:

class FileUploader(models.Model):
    file = models.FileField()
    name = models.CharField(max_length=100) #name is filename without extension
    version = models.IntegerField(default=0)
    upload_date = models.DateTimeField(auto_now=True, db_index=True)
    owner = models.ForeignKey('auth.User', related_name='uploaded_files')
    size = models.IntegerField(default=0)

Now, For APIs this is what I want:

  1. GET:

When I fire the GET endpoint, I want all above fields for every uploaded file.

  1. POST:

But for user to create/upload file, why she has to worry about passing all these fields. She can just upload the file and then, I suppose, serializer can get rest of the fields from uploaded FILE.

Searilizer: Question: I created below serializer to serve my purpose. But not sure if its the right way to implement it.

class FileUploaderSerializer(serializers.ModelSerializer):
    # overwrite = serializers.BooleanField()
    class Meta:
        model = FileUploader
        fields = ('file','name','version','upload_date', 'size')
        read_only_fields = ('name','version','owner','upload_date', 'size')

   def validate(self, validated_data):
        validated_data['owner'] = self.context['request'].user
        validated_data['name'] = os.path.splitext(validated_data['file'].name)[0]
        validated_data['size'] = validated_data['file'].size
        #other validation logic
        return validated_data

    def create(self, validated_data):
        return FileUploader.objects.create(**validated_data)

Viewset for reference:

class FileUploaderViewSet(viewsets.ModelViewSet):
    serializer_class = FileUploaderSerializer
    parser_classes = (MultiPartParser, FormParser,)

    # overriding default query set
    queryset = LayerFile.objects.all()

    def get_queryset(self, *args, **kwargs):
        qs = super(FileUploaderViewSet, self).get_queryset(*args, **kwargs)
        qs = qs.filter(owner=self.request.user)
        return qs

回答 5

根据我的经验,您不需要对文件字段做任何特别的事情,只需告诉它利用文件字段即可:

from rest_framework import routers, serializers, viewsets

class Photo(django.db.models.Model):
    file = django.db.models.ImageField()

    def __str__(self):
        return self.file.name

class PhotoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Photo
        fields = ('id', 'file')   # <-- HERE

class PhotoViewSet(viewsets.ModelViewSet):
    queryset = models.Photo.objects.all()
    serializer_class = PhotoSerializer

router = routers.DefaultRouter()
router.register(r'photos', PhotoViewSet)

api_urlpatterns = ([
    url('', include(router.urls)),
], 'api')
urlpatterns += [
    url(r'^api/', include(api_urlpatterns)),
]

您就可以上传文件了:

curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'

-F field=value为模型具有的每个其他字段添加。并且不要忘记添加身份验证。

From my experience, you don’t need to do anything particular about file fields, you just tell it to make use of the file field:

from rest_framework import routers, serializers, viewsets

class Photo(django.db.models.Model):
    file = django.db.models.ImageField()

    def __str__(self):
        return self.file.name

class PhotoSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Photo
        fields = ('id', 'file')   # <-- HERE

class PhotoViewSet(viewsets.ModelViewSet):
    queryset = models.Photo.objects.all()
    serializer_class = PhotoSerializer

router = routers.DefaultRouter()
router.register(r'photos', PhotoViewSet)

api_urlpatterns = ([
    url('', include(router.urls)),
], 'api')
urlpatterns += [
    url(r'^api/', include(api_urlpatterns)),
]

and you’re ready to upload files:

curl -sS http://example.com/api/photos/ -F 'file=@/path/to/file'

Add -F field=value for each extra field your model has. And don’t forget to add authentication.


回答 6

如果有人对ModelViewset for Django Rest Framework最简单的示例感兴趣。

该模型是

class MyModel(models.Model):
    name = models.CharField(db_column='name', max_length=200, blank=False, null=False, unique=True)
    imageUrl = models.FileField(db_column='image_url', blank=True, null=True, upload_to='images/')

    class Meta:
        managed = True
        db_table = 'MyModel'

序列化器

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = "__all__"

观点是,

class MyModelView(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

在邮递员中测试

If anyone interested in the easiest example with ModelViewset for Django Rest Framework.

The Model is,

class MyModel(models.Model):
    name = models.CharField(db_column='name', max_length=200, blank=False, null=False, unique=True)
    imageUrl = models.FileField(db_column='image_url', blank=True, null=True, upload_to='images/')

    class Meta:
        managed = True
        db_table = 'MyModel'

The Serializer,

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = "__all__"

And the View is,

class MyModelView(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

Test in Postman,


回答 7

在django-rest-framework中,请求数据由解析Parsers
http://www.django-rest-framework.org/api-guide/parsers/

默认情况下,django-rest-framework采用解析器类JSONParser。它将数据解析为json。因此,文件将不会被解析。
如果我们希望文件与其他数据一起被解析,则应使用以下解析器类之一。

FormParser
MultiPartParser
FileUploadParser

In django-rest-framework request data is parsed by the Parsers.
http://www.django-rest-framework.org/api-guide/parsers/

By default django-rest-framework takes parser class JSONParser. It will parse the data into json. so, files will not be parsed with it.
If we want files to be parsed along with other data we should use one of the below parser classes.

FormParser
MultiPartParser
FileUploadParser

回答 8

    from rest_framework import status
    from rest_framework.response import Response
    class FileUpload(APIView):
         def put(request):
             try:
                file = request.FILES['filename']
                #now upload to s3 bucket or your media file
             except Exception as e:
                   print e
                   return Response(status, 
                           status.HTTP_500_INTERNAL_SERVER_ERROR)
             return Response(status, status.HTTP_200_OK)
    from rest_framework import status
    from rest_framework.response import Response
    class FileUpload(APIView):
         def put(request):
             try:
                file = request.FILES['filename']
                #now upload to s3 bucket or your media file
             except Exception as e:
                   print e
                   return Response(status, 
                           status.HTTP_500_INTERNAL_SERVER_ERROR)
             return Response(status, status.HTTP_200_OK)

回答 9

def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
def post(self,request):
        serializer = ProductSerializer(data=request.DATA, files=request.FILES)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)

回答 10

我想写一个我觉得更干净,更易于维护的选项。我们将使用defaultRouter为我们的视图集添加CRUD网址,并且还将添加一个固定的网址,以指定同一视图集内的上载器视图。

**** views.py 

from rest_framework import viewsets, serializers
from rest_framework.decorators import action, parser_classes
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework_csv.parsers import CSVParser
from posts.models import Post
from posts.serializers import PostSerializer     


class PostsViewSet(viewsets.ModelViewSet):

    queryset = Post.objects.all()
    serializer_class = PostSerializer 
    parser_classes = (JSONParser, MultiPartParser, CSVParser)


    @action(detail=False, methods=['put'], name='Uploader View', parser_classes=[CSVParser],)
    def uploader(self, request, filename, format=None):
        # Parsed data will be returned within the request object by accessing 'data' attr  
        _data = request.data

        return Response(status=204)

项目的主要urls.py

**** urls.py 

from rest_framework import routers
from posts.views import PostsViewSet


router = routers.DefaultRouter()
router.register(r'posts', PostsViewSet)

urlpatterns = [
    url(r'^posts/uploader/(?P<filename>[^/]+)$', PostsViewSet.as_view({'put': 'uploader'}), name='posts_uploader')
    url(r'^', include(router.urls), name='root-api'),
    url('admin/', admin.site.urls),
]

.-自述文件。

当我们将@action装饰器添加到类方法’uploader’时,魔术发生了。通过指定“ methods = [‘put’]”参数,我们仅允许PUT请求;非常适合文件上传。

我还添加了参数“ parser_classes”,以显示可以选择将解析内容的解析器。我从rest_framework_csv包中添加了CSVParser,以演示如果需要此功能时我们如何仅接受某些类型的文件,在我的情况下,我仅接受“ Content-Type:text / csv”。注意:如果要添加自定义解析器,则需要在ViewSet的parsers_classes中指定它们,因为在访问上载器方法解析器之前,请求会将允许的media_type与主(类)解析器进行比较。

现在我们需要告诉Django如何使用此方法,以及可以在我们的url中实现的位置。那就是我们添加固定网址的时候(简单目的)。该网址将带有“文件名”参数,该参数稍后将在方法中传递。我们需要传递此方法“ uploader”,并在列表中指定http协议(’PUT’)到PostsViewSet.as_view方法。

当我们进入以下网址时

 http://example.com/posts/uploader/ 

它将期望一个带有标头的PUT请求,该标头指定“ Content-Type”和Content-Disposition:附件;filename =“ something.csv”。

curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"

I’d like to write another option that I feel is cleaner and easier to maintain. We’ll be using the defaultRouter to add CRUD urls for our viewset and we’ll add one more fixed url specifying the uploader view within the same viewset.

**** views.py 

from rest_framework import viewsets, serializers
from rest_framework.decorators import action, parser_classes
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework_csv.parsers import CSVParser
from posts.models import Post
from posts.serializers import PostSerializer     


class PostsViewSet(viewsets.ModelViewSet):

    queryset = Post.objects.all()
    serializer_class = PostSerializer 
    parser_classes = (JSONParser, MultiPartParser, CSVParser)


    @action(detail=False, methods=['put'], name='Uploader View', parser_classes=[CSVParser],)
    def uploader(self, request, filename, format=None):
        # Parsed data will be returned within the request object by accessing 'data' attr  
        _data = request.data

        return Response(status=204)

Project’s main urls.py

**** urls.py 

from rest_framework import routers
from posts.views import PostsViewSet


router = routers.DefaultRouter()
router.register(r'posts', PostsViewSet)

urlpatterns = [
    url(r'^posts/uploader/(?P<filename>[^/]+)$', PostsViewSet.as_view({'put': 'uploader'}), name='posts_uploader')
    url(r'^', include(router.urls), name='root-api'),
    url('admin/', admin.site.urls),
]

.- README.

The magic happens when we add @action decorator to our class method ‘uploader’. By specifying “methods=[‘put’]” argument, we are only allowing PUT requests; perfect for file uploading.

I also added the argument “parser_classes” to show you can select the parser that will parse your content. I added CSVParser from the rest_framework_csv package, to demonstrate how we can accept only certain type of files if this functionality is required, in my case I’m only accepting “Content-Type: text/csv”. Note: If you’re adding custom Parsers, you’ll need to specify them in parsers_classes in the ViewSet due the request will compare the allowed media_type with main (class) parsers before accessing the uploader method parsers.

Now we need to tell Django how to go to this method and where can be implemented in our urls. That’s when we add the fixed url (Simple purposes). This Url will take a “filename” argument that will be passed in the method later on. We need to pass this method “uploader”, specifying the http protocol (‘PUT’) in a list to the PostsViewSet.as_view method.

When we land in the following url

 http://example.com/posts/uploader/ 

it will expect a PUT request with headers specifying “Content-Type” and Content-Disposition: attachment; filename=”something.csv”.

curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"

回答 11

这是我已应用的一种方法,希望会对您有所帮助。

     class Model_File_update(APIView):
         parser_classes = (MultiPartParser, FormParser)
         permission_classes = [IsAuthenticated]  # it will check if the user is authenticated or not
         authentication_classes = [JSONWebTokenAuthentication]  # it will authenticate the person by JSON web token

         def put(self, request):
            id = request.GET.get('id')
            obj = Model.objects.get(id=id)
            serializer = Model_Upload_Serializer(obj, data=request.data)
            if serializer.is_valid():
               serializer.save()
               return Response(serializer.data, status=200)
            else:
               return Response(serializer.errors, status=400)

This is the one of the approach I’ve applied hopefully it’ll help.

     class Model_File_update(APIView):
         parser_classes = (MultiPartParser, FormParser)
         permission_classes = [IsAuthenticated]  # it will check if the user is authenticated or not
         authentication_classes = [JSONWebTokenAuthentication]  # it will authenticate the person by JSON web token

         def put(self, request):
            id = request.GET.get('id')
            obj = Model.objects.get(id=id)
            serializer = Model_Upload_Serializer(obj, data=request.data)
            if serializer.is_valid():
               serializer.save()
               return Response(serializer.data, status=200)
            else:
               return Response(serializer.errors, status=400)

回答 12

您可以通过生成解析器类来解析特定字段,然后将它们直接馈入标准DRF序列化器中,来概括@Nithin的答案以直接与DRF的现有序列化器系统一起工作:

from django.http import QueryDict
import json
from rest_framework import parsers


def gen_MultipartJsonParser(json_fields):
    class MultipartJsonParser(parsers.MultiPartParser):

        def parse(self, stream, media_type=None, parser_context=None):
            result = super().parse(
                stream,
                media_type=media_type,
                parser_context=parser_context
            )
            data = {}
            # find the data field and parse it
            qdict = QueryDict('', mutable=True)
            for json_field in json_fields:
                json_data = result.data.get(json_field, None)
                if not json_data:
                    continue
                data = json.loads(json_data)
                if type(data) == list:
                    for d in data:
                        qdict.update({json_field: d})
                else:
                    qdict.update({json_field: data})

            return parsers.DataAndFiles(qdict, result.files)

    return MultipartJsonParser

用法如下:

class MyFileViewSet(ModelViewSet):
    parser_classes = [gen_MultipartJsonParser(['tags', 'permissions'])]
    #                                           ^^^^^^^^^^^^^^^^^^^
    #                              Fields that need to be further JSON parsed
    ....

You can generalize @Nithin’s answer to work directly with DRF’s existing serializer system by generating a parser class to parse specific fields which are then fed directly into the standard DRF serializers:

from django.http import QueryDict
import json
from rest_framework import parsers


def gen_MultipartJsonParser(json_fields):
    class MultipartJsonParser(parsers.MultiPartParser):

        def parse(self, stream, media_type=None, parser_context=None):
            result = super().parse(
                stream,
                media_type=media_type,
                parser_context=parser_context
            )
            data = {}
            # find the data field and parse it
            qdict = QueryDict('', mutable=True)
            for json_field in json_fields:
                json_data = result.data.get(json_field, None)
                if not json_data:
                    continue
                data = json.loads(json_data)
                if type(data) == list:
                    for d in data:
                        qdict.update({json_field: d})
                else:
                    qdict.update({json_field: data})

            return parsers.DataAndFiles(qdict, result.files)

    return MultipartJsonParser

This is used like:

class MyFileViewSet(ModelViewSet):
    parser_classes = [gen_MultipartJsonParser(['tags', 'permissions'])]
    #                                           ^^^^^^^^^^^^^^^^^^^
    #                              Fields that need to be further JSON parsed
    ....

回答 13

如果您正在使用ModelViewSet,那么实际上您已经做好了!它为您处理一切!您只需要将该字段放入ModelSerializer中并content-type=multipart/form-data;在客户端中进行设置即可。

但您知道您不能以json格式发送文件。(在客户端中将content-type设置为application / json时)。除非您使用Base64格式。

因此,您有两种选择:

  • ModelViewSetModelSerializer处理工作,并使用发送请求content-type=multipart/form-data;
  • 将字段设置ModelSerializer为,Base64ImageField (or) Base64FileField并告诉您的客户端将文件编码为Base64并设置content-type=application/json

If you are using ModelViewSet, well actually you are done! It handles every things for you! You just need to put the field in your ModelSerializer and set content-type=multipart/form-data; in your client.

BUT as you know you can not send files in json format. (when content-type is set to application/json in your client). Unless you use Base64 format.

So you have two choices:

  • let ModelViewSet and ModelSerializer handle the job and send the request using content-type=multipart/form-data;
  • set the field in ModelSerializer as Base64ImageField (or) Base64FileField and tell your client to encode the file to Base64 and set the content-type=application/json

回答 14

models.py

from django.db import models

import uuid

class File(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    file = models.FileField(blank=False, null=False)
    
    def __str__(self):
        return self.file.name

serializers.py

from rest_framework import serializers
from .models import File

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = "__all__"

views.py

from django.shortcuts import render
from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from .serializers import FileSerializer


class FileUploadView(APIView):
    permission_classes = []
    parser_class = (FileUploadParser,)

    def post(self, request, *args, **kwargs):

      file_serializer = FileSerializer(data=request.data)

      if file_serializer.is_valid():
          file_serializer.save()
          return Response(file_serializer.data, status=status.HTTP_201_CREATED)
      else:
          return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

urls.py

from apps.files import views as FileViews

urlpatterns = [
    path('api/files', FileViews.FileUploadView.as_view()),
]

settings.py

# file uload parameters
MEDIA_URL =  '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

发送一个后发请求,api/files并将您的文件附加到form-data字段中file。该文件将被上载到/media文件夹,并且数据库记录将添加ID和文件名。

models.py

from django.db import models

import uuid

class File(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    file = models.FileField(blank=False, null=False)
    
    def __str__(self):
        return self.file.name

serializers.py

from rest_framework import serializers
from .models import File

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = "__all__"

views.py

from django.shortcuts import render
from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from .serializers import FileSerializer


class FileUploadView(APIView):
    permission_classes = []
    parser_class = (FileUploadParser,)

    def post(self, request, *args, **kwargs):

      file_serializer = FileSerializer(data=request.data)

      if file_serializer.is_valid():
          file_serializer.save()
          return Response(file_serializer.data, status=status.HTTP_201_CREATED)
      else:
          return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

urls.py

from apps.files import views as FileViews

urlpatterns = [
    path('api/files', FileViews.FileUploadView.as_view()),
]

settings.py

# file uload parameters
MEDIA_URL =  '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Send a post request to api/files with a your file attached to a form-data field file. The file will be uploaded to /media folder and a db record will be added with id and file name.


典型的AngularJS工作流程和项目结构(使用Python Flask)

问题:典型的AngularJS工作流程和项目结构(使用Python Flask)

对于整个MV *客户端框架的狂热,我还是一个新手。它不一定是AngularJS,但我选择它是因为它对我来说比淘汰赛,Ember或Backbone更自然。无论如何,工作流程是什么样的?人们是否开始在AngularJS中开发客户端应用程序,然后将其连接到后端?

还是相反,首先在Django,Flask,Rails中构建后端,然后将AngularJS应用附加到该后端?是否有“正确”的方式来做,还是最终只是个人喜好?

我也不确定是否根据Flask或AngularJS构建我的项目?社区实践。

例如,Flask的minitwit应用程序的结构如下:

minitwit
|-- minitwit.py
|-- static
   |-- css, js, images, etc...
`-- templates
   |-- html files and base layout

AngularJS教程应用程序的结构如下:

angular-phonecat
|-- app
    `-- css
    `-- img
    `-- js
    `-- lib
    `-- partials
    `-- index.html
|-- scripts
 `-- node.js server and test server files

我可以单独想象一个Flask应用程序,并且很容易看到像ToDo List这样的AngularJS应用程序,但是当同时使用这两种技术时,我不知道它们是如何协同工作的。当您已经拥有AngularJS时,似乎几乎不需要服务器端Web框架,一个简单的Python Web服务器就足够了。例如,在AngularJS待办应用程序中,他们使用MongoLab通过Restful API与数据库对话。无需在后端使用Web框架。

也许我只是非常困惑,而AngularJS只是一个花哨的jQuery库,所以我应该像在Flask项目中使用jQuery一样使用(假设我将AngularJS模板语法更改为与Jinja2不冲突的语法)。我希望我的问题有道理。我主要在后端工作,这个客户端框架对我来说是未知领域。

I am pretty new to this whole MV* client-side framework frenzy. It doesn’t have to be AngularJS, but I picked it because it feels more natural to me than either Knockout, Ember or Backbone. Anyway what is the workflow like? Do people start with developing a client-side application in AngularJS and then hooking up the back-end to it?

Or the other way around by first building the back-end in Django, Flask, Rails and then attaching an AngularJS app to it? Is there a “right” way of doing it, or is it just a personal preference in the end?

I am also not sure whether to structure my project according to the Flask or AngularJS? community practices.

For example, Flask’s minitwit app is structured like so:

minitwit
|-- minitwit.py
|-- static
   |-- css, js, images, etc...
`-- templates
   |-- html files and base layout

AngularJS tutorial app is structured like this:

angular-phonecat
|-- app
    `-- css
    `-- img
    `-- js
    `-- lib
    `-- partials
    `-- index.html
|-- scripts
 `-- node.js server and test server files

I could picture a Flask app by itself, and it’s fairly easy to see AngularJS app like ToDo List by itself but when it comes to using both of these technologies I don’t understand how they work together. It almost seems like I don’t need a server-side web-framework when you already have AngularJS, a simple Python web server will suffice. In the AngularJS to-do app for example they use MongoLab to talk to the database using Restful API. There was no need having a web framework on the back-end.

Maybe I am just awfully confused, and AngularJS is nothing more than a fancy jQuery library so I should use just like I would use jQuery in my Flask projects (assuming I change the AngularJS template syntax to something that doesn’t conflict with Jinja2). I hope my questions make some sense. I mainly work on the back-end and this client-side framework is an unknown territory for me.


回答 0

首先,我将以标准结构组织Flask应用程序,如下所示:

app
|-- app.py
|-- static
    |-- css
    |-- img
    |-- js
|-- templates

正如btford所提到的,如果您正在开发Angular应用程序,则需要集中精力使用Angular客户端模板,而远离服务器端模板。使用render_template(’index.html’)会使Flask将您的角度模板解释为Jinja模板,因此它们将无法正确渲染。相反,您需要执行以下操作:

@app.route("/")
def index():
    return send_file('templates/index.html')

请注意,使用send_file()意味着文件将被缓存,因此您至少在开发时可能要使用make_response():

    return make_response(open('templates/index.html').read())

然后,构建应用程序的AngularJS部分,修改应用程序结构,使其看起来像这样:

app
|-- app.py
|-- static
    |-- css
    |-- img
    |-- js
        |-- app.js, controllers.js, etc.
    |-- lib
        |-- angular
            |-- angular.js, etc.
    |-- partials
|-- templates
    |-- index.html

确保index.html包含AngularJS以及任何其他文件:

<script src="static/lib/angular/angular.js"></script>

此时,您尚未构建RESTful API,因此可以让js控制器返回预定义的示例数据(仅是临时设置)。准备就绪后,实现RESTful API并使用angular-resource.js将其连接到您的angular应用程序。

编辑:我整理了一个应用程序模板,尽管比我上面描述的要复杂一些,但它说明了如何使用AngularJS + Flask构建应用程序,并完成了AngularJS和简单的Flask API之间的通信。如果您想签出,这里是这里:https : //github.com/rxl/angular-flask

I would start out by organizing the Flask app in the standard structure as follows:

app
|-- app.py
|-- static
    |-- css
    |-- img
    |-- js
|-- templates

And as btford mentioned, if you are doing an Angular app, you’ll want to focus on using Angular client-side templates and stay away from server-side templates. Using render_template(‘index.html’) will cause Flask to interpret your angular templates as jinja templates, so they won’t render correctly. Instead, you’ll want to do the following:

@app.route("/")
def index():
    return send_file('templates/index.html')

Note that using send_file() means that the files will be cached, so you might want to use make_response() instead, at least for development:

    return make_response(open('templates/index.html').read())

Afterwards, build out the AngularJS part of your app, modifying the app structure so that it looks like this:

app
|-- app.py
|-- static
    |-- css
    |-- img
    |-- js
        |-- app.js, controllers.js, etc.
    |-- lib
        |-- angular
            |-- angular.js, etc.
    |-- partials
|-- templates
    |-- index.html

Make sure your index.html includes AngularJS, as well as any other files:

<script src="static/lib/angular/angular.js"></script>

At this point, you haven’t yet constructed your RESTful API, so you can have your js controllers return predefined sample data (only a temporary setup). When you’re ready, implement the RESTful API and hook it up to your angular app with angular-resource.js.

EDIT: I put together an app template that, though a little more complex that what I’ve described above, illustrates how one could build an app with AngularJS + Flask, complete with communication between AngularJS and a simple Flask API. Here it is if you want to check it out: https://github.com/rxl/angular-flask


回答 1

您可以从任一端开始。

没错,AngularJS可能不需要完整的服务器端框架。通常,最好提供静态HTML / CSS / JavaScript文件,并为后端提供RESTful API供客户端使用。您可能应该避免的一件事是将服务器端模板与AngularJS客户端模板混合。

如果您想使用Flask来提供文件(可能有些过头,但仍然可以使用它),则可以将“ app”的内容从“ angular-phonecat”复制到“ minitwit”的“ static”文件夹中。

AngularJS的目标是类似AJAX的应用程序,而flask使您能够执行旧式Web应用程序以及创建RESTful API。每种方法都有优点和缺点,因此它实际上取决于您要执行的操作。如果您给我一些见解,我也许可以提出更多建议。

You can start on either end.

You are right that you probably don’t need a full server-side framework with AngularJS. It’s typically better to serve static HTML/CSS/JavaScript files, and provide a RESTful API for the back end for the client to consume. One thing that you should probably avoid is mixing server-side templates with AngularJS client-side templates.

If you want to use Flask to serve your files (might be overkill, but you can use it nonetheless) you would copy the contents of “app” from “angular-phonecat” into the “static” folder of “minitwit.”

AngularJS is more targeted at AJAX-like applications, whereas flask gives you the ability to do both the older-style web apps as well as create RESTful APIs. There are advantages and disadvantages to each approach, so it really depends what you want to do. If you give me some insights, I might be able to make further recommendations.


回答 2

John Lindquist的这个Jetbrains PyCharm官方视频(angular.js和jetbrains大师)是一个不错的起点,因为它显示了烧瓶中Web服务,数据库和angular.js的相互作用。

他在不到25分钟的时间内使用flask,sqlalchemy,flask-restless和angular.js 构建了一个pinterest克隆

欣赏:http//www.youtube.com/watch?v = 2geC50roans

This official Jetbrains PyCharm video by John Lindquist (angular.js and jetbrains guru) is a nice starting point as it shows the interplay of webservice, database and angular.js within flask.

He builds a pinterest clone with flask, sqlalchemy, flask-restless and angular.js in less than 25 minutes.

Enjoy: http://www.youtube.com/watch?v=2geC50roans


回答 3

编辑:新的Angular2样式指南更详细地建议了一个相似的结构,即使不是相同的结构。

下面的答案针对大型项目。我花了很多时间思考和尝试几种方法,因此我可以将一些用于后端功能的服务器端框架(在我的情况下为Flask和App Engine)与客户端框架(例如Angular)结合起来。这两个答案都很好,但是我想提出一个稍微不同的方法(至少在我看来),以一种更人性化的方式进行扩展。

当您实现TODO示例时,事情就很简单了。当您开始添加功能以及改善用户体验的细微细节时,不难发现样式,javascript等混乱。

我的应用程序开始变得很大,因此我不得不退后一步并重新思考。最初,通过将所有样式和所有JavaScript合并在一起,可以采用上述建议的方法,但是这种方法不是模块化的,也不容易维护。

如果我们按功能而非文件类型组织客户端代码怎么办:

app
|-- server
    |-- controllers
        |-- app.py
    |-- models
        |-- model.py
    |-- templates
        |-- index.html
|-- static
    |-- img
    |-- client
        |-- app.js
        |-- main_style.css
        |-- foo_feature
            |-- controller.js
            |-- directive.js
            |-- service.js
            |-- style.css
            |-- html_file.tpl.html
        |-- bar_feature
            |-- controller.js
            |-- directive.js
            |-- service.js
            |-- style.css
            |-- html_file.tpl.html
    |-- lib
        |-- jquery.js
        |-- angular.js
        |-- ...

等等。

如果我们这样构建它,我们可以将我们的每个目录都包装在angular模块中。而且我们以一种很好的方式拆分了文件,当我们使用特定功能时,我们不必经过不相关的代码。

正确配置了诸如Grunt的任务运行程序,将能够轻松找到并连接和编译文件。

edit: The new Angular2 style guide suggests a similar, if not the same structure in much more detail.

The answer below target large scale projects. I have spend quite some time thinking and experimenting with several approaches so I can combine some server side framework (Flask with App Engine in my case) for back-end functionality along with a client side framework like Angular. Both answers are very good, but I would like to suggest a slightly different approach which (in my mind at least) scales in a more human way.

When you are implementing a TODO example, things are quite straight forward. When you start adding functionality though and small nice details for user experience improvement, its not difficult to get lost in chaos of styles, javascript etc..

My application started to grow quite big, so I had to take a step back and rethink. Initially an approach like suggested above would work, by having all the styles together and all JavaScript together, but its not modular and not easily maintainable.

What if we organized the client code per feature and not per file type:

app
|-- server
    |-- controllers
        |-- app.py
    |-- models
        |-- model.py
    |-- templates
        |-- index.html
|-- static
    |-- img
    |-- client
        |-- app.js
        |-- main_style.css
        |-- foo_feature
            |-- controller.js
            |-- directive.js
            |-- service.js
            |-- style.css
            |-- html_file.tpl.html
        |-- bar_feature
            |-- controller.js
            |-- directive.js
            |-- service.js
            |-- style.css
            |-- html_file.tpl.html
    |-- lib
        |-- jquery.js
        |-- angular.js
        |-- ...

and so on.

If we build it like this, we can wrap every directory of ours in an angular module. And we have split our files in a nice way that we don’t have to go through irrelevant code when we are working with a specific feature.

A task runner like Grunt properly configured, will be able to find and concatenate and compile your files without much hassle.


回答 4

另一种选择是将两者完全分开。

项目
|-服务器
|-客户

与flask相关的文件位于服务器文件夹下,而与angularjs相关的文件位于客户端文件夹下。这样,将更容易更改后端或前端。例如,将来您可能希望从Flask切换到Django或从AngularJS切换到ReactJS。

Another option is to completely separate the two.

project
|-- server
|-- client

Files related to flask goes under the server folder and files related to angularjs goes under the client folder. This way, it will be easier to change the backend or front-end. For example, you might want to switch from Flask to Django or AngularJS to ReactJS in the future.


回答 5

我认为确定大部分数据处理的目标是前端还是后端很重要。
如果是前端,则使用有角度的工作流程,这意味着您的flask应用程序将更多地充当api的角色,在该api中,如flask-restful的扩展将结束。

但是,如果像我一样,您需要在后端完成大部分工作,然后使用flask结构,仅插入有角度的插件(或在我的情况下为vue.js)来构建前端(必要时)

I think it is important to determine at what end you want to do most of your data processing – front end or back end.
If it’s front end, then go with the angular workflow, which means your flask app will function as more of an api where an extension like flask-restful will come end.

But if like me, you are doing most work on the backend, then go with the flask structure and only plug angular ( or in my case vue.js) to build the front end (when neccessary)


Oppia-一个免费的在线学习平台,让所有人都能获得优质教育

Oppia是一个在线学习工具,任何人都可以轻松地创建和分享互动活动(称为“探索”)。这些活动模拟与导师的一对一对话,使学生能够边做边学,同时获得反馈

除了开发Oppia平台外,团队还在开发和试点一套免费有效的lessons基础数学。这些课程针对的是无法获得教育资源的学习者。

Oppia是使用Python和AngularJS编写的,构建在Google App engine之上



安装

请参阅Installing Oppia page有关完整说明,请参阅

贡献

奥皮亚项目是由社区为社区建造的。我们欢迎每个人的贡献,特别是新的贡献者。

您可以在很多方面帮助Oppia的开发,包括艺术、编码、设计和文档

支持

如果您有任何功能请求或错误报告,请将它们记录在我们的issue tracker

请将安全问题直接报告给admin@oppia.org

许可证

Oppia代码在Apache v2 license

保持联系

我们在Gitter上也有公共聊天室:https://gitter.im/oppia/oppia-chat顺便过来打个招呼!