标签归档:Django

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.


Django Rest Framework-未提供身份验证凭据

问题:Django Rest Framework-未提供身份验证凭据

我正在使用Django Rest Framework开发API。我试图列出或创建“订单”对象,但是当我尝试访问控制台时,出现此错误:

{"detail": "Authentication credentials were not provided."}

观看次数:

from django.shortcuts import render
from rest_framework import viewsets
from django.contrib.auth.models import User
from rest_framework.renderers import JSONRenderer, YAMLRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from order.models import *
from API.serializers import *
from rest_framework.permissions import IsAuthenticated

class OrderViewSet(viewsets.ModelViewSet):
    model = Order
    serializer_class = OrderSerializer
    permission_classes = (IsAuthenticated,)

序列化器:

class OrderSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Order
        fields = ('field1', 'field2')

我的网址:

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url
from django.conf import settings
from django.contrib import admin
from django.utils.functional import curry
from django.views.defaults import *
from rest_framework import routers
from API.views import *

admin.autodiscover()

handler500 = "web.views.server_error"
handler404 = "web.views.page_not_found_error"

router = routers.DefaultRouter()
router.register(r'orders', OrdersViewSet)

urlpatterns = patterns('',
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token'),
    url(r'^api/', include(router.urls)),
)

然后我在控制台中使用以下命令:

curl -X GET http://127.0.0.1:8000/api/orders/ -H 'Authorization: Token 12383dcb52d627eabd39e7e88501e96a2sadc55'

错误说:

{"detail": "Authentication credentials were not provided."}

I’m developing an API using Django Rest Framework. I’m trying to list or create an “Order” object, but when i’m trying to access the console gives me this error:

{"detail": "Authentication credentials were not provided."}

Views:

from django.shortcuts import render
from rest_framework import viewsets
from django.contrib.auth.models import User
from rest_framework.renderers import JSONRenderer, YAMLRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from order.models import *
from API.serializers import *
from rest_framework.permissions import IsAuthenticated

class OrderViewSet(viewsets.ModelViewSet):
    model = Order
    serializer_class = OrderSerializer
    permission_classes = (IsAuthenticated,)

Serializer:

class OrderSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Order
        fields = ('field1', 'field2')

And my URLs:

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url
from django.conf import settings
from django.contrib import admin
from django.utils.functional import curry
from django.views.defaults import *
from rest_framework import routers
from API.views import *

admin.autodiscover()

handler500 = "web.views.server_error"
handler404 = "web.views.page_not_found_error"

router = routers.DefaultRouter()
router.register(r'orders', OrdersViewSet)

urlpatterns = patterns('',
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token'),
    url(r'^api/', include(router.urls)),
)

And then I’m using this command in the console:

curl -X GET http://127.0.0.1:8000/api/orders/ -H 'Authorization: Token 12383dcb52d627eabd39e7e88501e96a2sadc55'

And the error say:

{"detail": "Authentication credentials were not provided."}

回答 0

如果您使用mod_wsgi在Apache上运行Django,则必须添加

WSGIPassAuthorization On

在您的httpd.conf中。否则,授权标头将被mod_wsgi剥离。

If you are runnig Django on Apache using mod_wsgi you have to add

WSGIPassAuthorization On

in your httpd.conf. Otherwise authorization header will be stripped out by mod_wsgi.


回答 1

通过在我的settings.py中添加“ DEFAULT_AUTHENTICATION_CLASSES”来解决

REST_FRAMEWORK = {
   'DEFAULT_AUTHENTICATION_CLASSES': (
       'rest_framework.authentication.TokenAuthentication',
   ),
   'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser'
   ),
}

Solved by adding “DEFAULT_AUTHENTICATION_CLASSES” to my settings.py

REST_FRAMEWORK = {
   'DEFAULT_AUTHENTICATION_CLASSES': (
       'rest_framework.authentication.TokenAuthentication',
   ),
   'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser'
   ),
}

回答 2

这可以帮助我在settings.py中没有“ DEFAULT_PERMISSION_CLASSES”的情况

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'PAGE_SIZE': 10
}

This help me out without “DEFAULT_PERMISSION_CLASSES” in my settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'PAGE_SIZE': 10
}

回答 3

仅对于其他人,由于同样的错误而以同样的错误登陆,如果您request.user是您AnonymousUser的用户,而不是实际有权访问URL的正确用户,则可能会出现此问题。您可以通过打印的值看到这一点request.user。如果确实是匿名用户,这些步骤可能会有所帮助:

  1. 请确保您有 'rest_framework.authtoken'INSTALLED_APPS你的settings.py

  2. 确保您在settings.py以下位置有此商品:

    REST_FRAMEWORK = {
    
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.TokenAuthentication',
            # ...
        ),
    
        # ...
    }
  3. 确保您具有登录用户的正确令牌。如果没有令牌,请在此处了解如何获取令牌。基本上,POST如果您提供正确的用户名和密码,则需要对视图进行请求,该视图将为您提供令牌。例:

    curl -X POST -d "user=Pepe&password=aaaa"  http://localhost:8000/
  4. 确保您尝试访问的视图具有以下内容:

    class some_fancy_example_view(ModelViewSet): 
    """
    not compulsary it has to be 'ModelViewSet' this can be anything like APIview etc, depending on your requirements.
    """
        permission_classes = (IsAuthenticated,) 
        authentication_classes = (TokenAuthentication,) 
        # ...
  5. curl现在以这种方式使用:

    curl -X (your_request_method) -H  "Authorization: Token <your_token>" <your_url>

例:

    curl -X GET http://127.0.0.1:8001/expenses/  -H "Authorization: Token 9463b437afdd3f34b8ec66acda4b192a815a15a8"

Just for other people landing up here with same error, this issue can arise if your request.user is AnonymousUser and not the right user who is actually authorized to access the URL. You can see that by printing value of request.user . If it is indeed an anonymous user, these steps might help:

  1. Make sure you have 'rest_framework.authtoken' in INSTALLED_APPS in your settings.py.

  2. Make sure you have this somewhere in settings.py:

    REST_FRAMEWORK = {
    
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.TokenAuthentication',
            # ...
        ),
    
        # ...
    }
    
  3. Make sure you have the correct token for the user who is logged in. If you do not have the token, learn how to get it here. Basically, you need to do a POST request to a view which gives you the token if you provide the correct username and password. Example:

    curl -X POST -d "user=Pepe&password=aaaa"  http://localhost:8000/
    
  4. Make sure the view which you are trying to access, has these:

    class some_fancy_example_view(ModelViewSet): 
    """
    not compulsary it has to be 'ModelViewSet' this can be anything like APIview etc, depending on your requirements.
    """
        permission_classes = (IsAuthenticated,) 
        authentication_classes = (TokenAuthentication,) 
        # ...
    
  5. Use curl now this way:

    curl -X (your_request_method) -H  "Authorization: Token <your_token>" <your_url>
    

Example:

    curl -X GET http://127.0.0.1:8001/expenses/  -H "Authorization: Token 9463b437afdd3f34b8ec66acda4b192a815a15a8"

回答 4

如果您在命令行中玩耍(使用curl或HTTPie等),则可以使用BasicAuthentication测试/使用您的API

    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.BasicAuthentication',  # enables simple command line authentication
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.TokenAuthentication',
        )
    }

然后可以使用curl

curl --user user:password -X POST http://example.com/path/ --data "some_field=some data"

httpie(在眼睛上更容易):

http -a user:password POST http://example.com/path/ some_field="some data"

或其他类似Advanced Rest Client(ARC)的东西

If you are playing around in the command line (using curl, or HTTPie etc) you can use BasicAuthentication to test/user your API

    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.BasicAuthentication',  # enables simple command line authentication
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.TokenAuthentication',
        )
    }

You can then use curl

curl --user user:password -X POST http://example.com/path/ --data "some_field=some data"

or httpie (its easier on the eyes):

http -a user:password POST http://example.com/path/ some_field="some data"

or something else like Advanced Rest Client (ARC)


回答 5

我也遇到了同样的事情,因为我错过了添加

authentication_classes =(令牌认证)

在我的API视图类中。

class ServiceList(generics.ListCreateAPIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication, TokenAuthentication)
    queryset = Service.objects.all()
    serializer_class = ServiceSerializer
    permission_classes = (IsAdminOrReadOnly,)

除了上述内容外,我们还需要在settings.py文件中明确告知Django 身份验证

REST_FRAMEWORK = {
   'DEFAULT_AUTHENTICATION_CLASSES': (
   'rest_framework.authentication.TokenAuthentication',
   )
}

I too faced the same since I missed adding

authentication_classes = (TokenAuthentication)

in my API view class.

class ServiceList(generics.ListCreateAPIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication, TokenAuthentication)
    queryset = Service.objects.all()
    serializer_class = ServiceSerializer
    permission_classes = (IsAdminOrReadOnly,)

In addition to the above, we need to explicitly tell Django about the Authentication in settings.py file.

REST_FRAMEWORK = {
   'DEFAULT_AUTHENTICATION_CLASSES': (
   'rest_framework.authentication.TokenAuthentication',
   )
}

回答 6

在settings.py中添加SessionAuthentication就可以了

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', ), }

Adding SessionAuthentication in settings.py will do the job

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', ), }


回答 7

由于它是会话登录,因此您需要提供凭据,因此可以先进行 127.0.0:8000/admin 管理员登录,然后再正常运行

Since it is session Login so you need to provide you credentials so do 127.0.0:8000/admin admin and login later it will work fine


回答 8

对我来说,我必须在Django DRF上的Authorization标头前面加上“ JWT”,而不是“ Bearer”或“ Token”。然后它开始工作。例如-

Authorization: JWT asdflkj2ewmnsasdfmnwelfkjsdfghdfghdv.wlsfdkwefojdfgh

For me, I had to prepend my Authorization header with “JWT” instead of “Bearer” or “Token” on Django DRF. Then it started working. eg –

Authorization: JWT asdflkj2ewmnsasdfmnwelfkjsdfghdfghdv.wlsfdkwefojdfgh


回答 9

如果你正在使用authentication_classes,那么你应该有is_active作为TrueUser模型,这可能是False默认。

If you are using authentication_classes then you should have is_active as True in User model, which might be False by default.


Django使用get_user_model与settings.AUTH_USER_MODEL

问题:Django使用get_user_model与settings.AUTH_USER_MODEL

阅读Django文档:

get_user_model()

而不是直接引用用户,您应该使用django.contrib.auth.get_user_model()引用用户模型。此方法将返回当前活动的用户模型-如果指定了自定义用户模型,则返回用户模型,否则返回用户。

在定义与用户模型的外键或多对多关系时,应使用AUTH_USER_MODEL设置指定自定义模型。

我对以上文字感到困惑。我应该这样做吗?

author = models.ForeignKey(settings.AUTH_USER_MODEL)

或这个…

author = models.ForeignKey(get_user_model())

两者似乎都起作用。

Reading the Django Documentation:

get_user_model()

Instead of referring to User directly, you should reference the user model using django.contrib.auth.get_user_model(). This method will return the currently active User model – the custom User model if one is specified, or User otherwise.

When you define a foreign key or many-to-many relations to the User model, you should specify the custom model using the AUTH_USER_MODEL setting.

I’m confused with the above text. Should I be doing this:

author = models.ForeignKey(settings.AUTH_USER_MODEL)

or this…

author = models.ForeignKey(get_user_model())

Both seem to work.


回答 0

使用settings.AUTH_USER_MODEL会延迟实际模型类的检索,直到所有应用程序都加载完毕。get_user_model会在您首次导入应用程序时尝试检索模型类。

get_user_model无法保证User模型已经加载到应用程序缓存中。它可能会在您的特定设置中起作用,但这是一个命中注定的情况。如果您更改某些设置(例如的顺序INSTALLED_APPS),则很可能会中断导入,您将不得不花费更多的时间进行调试。

settings.AUTH_USER_MODEL 将传递一个字符串作为外键模型,并且如果在导入该外键时模型类的检索失败,则检索将被延迟,直到将所有模型类都加载到缓存中为止。

Using settings.AUTH_USER_MODEL will delay the retrieval of the actual model class until all apps are loaded. get_user_model will attempt to retrieve the model class at the moment your app is imported the first time.

get_user_model cannot guarantee that the User model is already loaded into the app cache. It might work in your specific setup, but it is a hit-and-miss scenario. If you change some settings (e.g. the order of INSTALLED_APPS) it might very well break the import and you will have to spend additional time debugging.

settings.AUTH_USER_MODEL will pass a string as the foreign key model, and if the retrieval of the model class fails at the time this foreign key is imported, the retrieval will be delayed until all model classes are loaded into the cache.


回答 1

自Django 1.11起新增。

从Django 1.11开始,您可以get_user_model()在两种情况下使用!因此,如果您不想再为它烦恼,那就接受它。

“在两种情况下”表示:是否需要用户模型来访问其属性,以及是否要定义ForeignKey / ManyToMany关系。

变更日志

现在即使在定义模型的模块中,也可以在导入时调用get_user_model()。

所以…还有使用理由settings.AUTH_USER_MODEL吗?嗯,文档仍然建议使用settings.AUTH_USER_MODEL(这是一个字符串)来定义关系,但是没有给出明确的原因。可能对性能有益,但似乎并不重要。

代码示例:

from django.db import models
from django.contrib.auth import get_user_model
...
    ...
    user = models.ForeignKey(
        get_user_model(),
        null=True, # explicitly set null, since it's required in django 2.x. - otherwise migrations will be incompatible later!
        ...
    )

New since Django 1.11.

Since Django 1.11 you can use get_user_model() in both cases! So if you don’t want to bother about it further, just take it.

“in both cases” means: if you need the user model for accessing its attributes, as well as if you want to define a ForeignKey/ManyToMany-relation.

From the changelog:

get_user_model() can now be called at import time, even in modules that define models.

so… is there still a reason to use settings.AUTH_USER_MODEL? Well, the docs still recommend the settings.AUTH_USER_MODEL (which is a string) for defining relations, but without giving an explicit reason. Might be beneficial for performance, but doesn’t seem to matter much.

Code example:

from django.db import models
from django.contrib.auth import get_user_model
...
    ...
    user = models.ForeignKey(
        get_user_model(),
        null=True, # explicitly set null, since it's required in django 2.x. - otherwise migrations will be incompatible later!
        ...
    )

回答 2

从Django 1.11开始,get_user_model()实际上使用settings.AUTH_USER_MODEL

def get_user_model():
    """
    Return the User model that is active in this project.
    """
    try:
        return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False)
    except ValueError:
        raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
    except LookupError:
        raise ImproperlyConfigured(
            "AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL
        )

Since Django 1.11, get_user_model() actually uses settings.AUTH_USER_MODEL:

def get_user_model():
    """
    Return the User model that is active in this project.
    """
    try:
        return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False)
    except ValueError:
        raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
    except LookupError:
        raise ImproperlyConfigured(
            "AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL
        )

回答 3

settings.AUTH_USER_MODEL返回一个字符串(用户模型的位置),例如“ user_accounts.User”

get_user_model()返回ACTUAL模型类,而不是字符串。

因此,在需要用户模型的情况下,请使用get_user_model()。如果需要它的位置(module.model作为字符串),请使用settings.AUTH_USER_MODEL。

settings.AUTH_USER_MODEL returns a string (the location of the User model) e.g. ‘user_accounts.User’

get_user_model() returns the ACTUAL model class, not a string.

So in cases where you need the User model, use get_user_model(). If you need it’s location (module.model as a string), use the settings.AUTH_USER_MODEL .


回答 4

如果未设置AUTH_USER_MODEL,则可以回退到默认用户模型:

from django.conf import settings
from django.contrib.auth.models import User

USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', User)

A way to fallback to the default user model if AUTH_USER_MODEL is not set:

from django.conf import settings
from django.contrib.auth.models import User

USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', User)

生成文件以使用Django下载

问题:生成文件以使用Django下载

是否可以制作一个zip归档文件并提供下载,但仍不将文件保存到硬盘驱动器?

Is it possible to make a zip archive and offer it to download, but still not save a file to the hard drive?


回答 0

要触发下载,您需要设置Content-Disposition标题:

from django.http import HttpResponse
from wsgiref.util import FileWrapper

# generate the file
response = HttpResponse(FileWrapper(myfile.getvalue()), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename=myfile.zip'
return response

如果您不想将文件放在磁盘上,则需要使用 StringIO

import cStringIO as StringIO

myfile = StringIO.StringIO()
while not_finished:
    # generate chunk
    myfile.write(chunk)

您也可以选择设置Content-Length标头:

response['Content-Length'] = myfile.tell()

To trigger a download you need to set Content-Disposition header:

from django.http import HttpResponse
from wsgiref.util import FileWrapper

# generate the file
response = HttpResponse(FileWrapper(myfile.getvalue()), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename=myfile.zip'
return response

If you don’t want the file on disk you need to use StringIO

import cStringIO as StringIO

myfile = StringIO.StringIO()
while not_finished:
    # generate chunk
    myfile.write(chunk)

Optionally you can set Content-Length header as well:

response['Content-Length'] = myfile.tell()

回答 1

您将更高兴创建一个临时文件。这样可以节省大量内存。当您同时拥有一个或两个以上用户时,您会发现节省内存非常重要。

但是,您可以写入StringIO对象。

>>> import zipfile
>>> import StringIO
>>> buffer= StringIO.StringIO()
>>> z= zipfile.ZipFile( buffer, "w" )
>>> z.write( "idletest" )
>>> z.close()
>>> len(buffer.getvalue())
778

“缓冲区”对象类似于具有778字节ZIP存档的文件。

You’ll be happier creating a temporary file. This saves a lot of memory. When you have more than one or two users concurrently, you’ll find the memory saving is very, very important.

You can, however, write to a StringIO object.

>>> import zipfile
>>> import StringIO
>>> buffer= StringIO.StringIO()
>>> z= zipfile.ZipFile( buffer, "w" )
>>> z.write( "idletest" )
>>> z.close()
>>> len(buffer.getvalue())
778

The “buffer” object is file-like with a 778 byte ZIP archive.


回答 2

为什么不制作tar文件呢?像这样:

def downloadLogs(req, dir):
    response = HttpResponse(content_type='application/x-gzip')
    response['Content-Disposition'] = 'attachment; filename=download.tar.gz'
    tarred = tarfile.open(fileobj=response, mode='w:gz')
    tarred.add(dir)
    tarred.close()

    return response

Why not make a tar file instead? Like so:

def downloadLogs(req, dir):
    response = HttpResponse(content_type='application/x-gzip')
    response['Content-Disposition'] = 'attachment; filename=download.tar.gz'
    tarred = tarfile.open(fileobj=response, mode='w:gz')
    tarred.add(dir)
    tarred.close()

    return response

回答 3

是的,您可以使用zipfile模块zlib模块或其他压缩模块在内存中创建一个zip存档。您可以使视图将zip归档文件写入HttpResponseDjango视图返回的对象,而不是将上下文发送到模板。最后,您需要将mimetype设置为适当的格式,以告知浏览器将响应视为file

Yes, you can use the zipfile module, zlib module or other compression modules to create a zip archive in memory. You can make your view write the zip archive to the HttpResponse object that the Django view returns instead of sending a context to a template. Lastly, you’ll need to set the mimetype to the appropriate format to tell the browser to treat the response as a file.


回答 4

models.py

from django.db import models

class PageHeader(models.Model):
    image = models.ImageField(upload_to='uploads')

views.py

from django.http import HttpResponse
from StringIO import StringIO
from models import *
import os, mimetypes, urllib

def random_header_image(request):
    header = PageHeader.objects.order_by('?')[0]
    image = StringIO(file(header.image.path, "rb").read())
    mimetype = mimetypes.guess_type(os.path.basename(header.image.name))[0]

    return HttpResponse(image.read(), mimetype=mimetype)

models.py

from django.db import models

class PageHeader(models.Model):
    image = models.ImageField(upload_to='uploads')

views.py

from django.http import HttpResponse
from StringIO import StringIO
from models import *
import os, mimetypes, urllib

def random_header_image(request):
    header = PageHeader.objects.order_by('?')[0]
    image = StringIO(file(header.image.path, "rb").read())
    mimetype = mimetypes.guess_type(os.path.basename(header.image.name))[0]

    return HttpResponse(image.read(), mimetype=mimetype)

回答 5


回答 6

def download_zip(request,file_name):
    filePath = '<path>/'+file_name
    fsock = open(file_name_with_path,"rb")
    response = HttpResponse(fsock, content_type='application/zip')
    response['Content-Disposition'] = 'attachment; filename=myfile.zip'
    return response

您可以根据需要替换zip和内容类型。

def download_zip(request,file_name):
    filePath = '<path>/'+file_name
    fsock = open(file_name_with_path,"rb")
    response = HttpResponse(fsock, content_type='application/zip')
    response['Content-Disposition'] = 'attachment; filename=myfile.zip'
    return response

You can replace zip and content type as per your requirement.


回答 7

与内存中的tgz存档相同:

import tarfile
from io import BytesIO


def serve_file(request):
    out = BytesIO()
    tar = tarfile.open(mode = "w:gz", fileobj = out)
    data = 'lala'.encode('utf-8')
    file = BytesIO(data)
    info = tarfile.TarInfo(name="1.txt")
    info.size = len(data)
    tar.addfile(tarinfo=info, fileobj=file)
    tar.close()

    response = HttpResponse(out.getvalue(), content_type='application/tgz')
    response['Content-Disposition'] = 'attachment; filename=myfile.tgz'
    return response

Same with in memory tgz archive:

import tarfile
from io import BytesIO


def serve_file(request):
    out = BytesIO()
    tar = tarfile.open(mode = "w:gz", fileobj = out)
    data = 'lala'.encode('utf-8')
    file = BytesIO(data)
    info = tarfile.TarInfo(name="1.txt")
    info.size = len(data)
    tar.addfile(tarinfo=info, fileobj=file)
    tar.close()

    response = HttpResponse(out.getvalue(), content_type='application/tgz')
    response['Content-Disposition'] = 'attachment; filename=myfile.tgz'
    return response

单个Django ModelForm中有多个模型?

问题:单个Django ModelForm中有多个模型?

ModelFormDjango 是否可以在一个模型中包含多个模型?我正在尝试创建个人资料编辑表单。因此,我需要包括User模型 UserProfile模型中的某些字段。目前我正在使用2种形式

class UserEditForm(ModelForm):

    class Meta:
        model = User
        fields = ("first_name", "last_name")

class UserProfileForm(ModelForm):

    class Meta:
        model = UserProfile
        fields = ("middle_name", "home_phone", "work_phone", "cell_phone")

有没有一种方法可以将这些合并为一个表单,或者我是否只需要创建一个表单并处理数据库加载并保存自己?

Is it possible to have multiple models included in a single ModelForm in django? I am trying to create a profile edit form. So I need to include some fields from the User model and the UserProfile model. Currently I am using 2 forms like this

class UserEditForm(ModelForm):

    class Meta:
        model = User
        fields = ("first_name", "last_name")

class UserProfileForm(ModelForm):

    class Meta:
        model = UserProfile
        fields = ("middle_name", "home_phone", "work_phone", "cell_phone")

Is there a way to consolidate these into one form or do I just need to create a form and handle the db loading and saving myself?


回答 0

您可以只在一个<form>html元素内的模板中显示两种形式。然后,只需在视图中分别处理表单即可。您仍然可以使用数据库,form.save()而不必自己进行数据库加载和保存。

在这种情况下,您不需要它,但是如果您要使用具有相同字段名的表单,请查看prefixdjango表单的kwarg。(我在这里回答了一个问题)。

You can just show both forms in the template inside of one <form> html element. Then just process the forms separately in the view. You’ll still be able to use form.save() and not have to process db loading and saving yourself.

In this case you shouldn’t need it, but if you’re going to be using forms with the same field names, look into the prefix kwarg for django forms. (I answered a question about it here).


回答 1

您可以尝试使用以下代码:

class CombinedFormBase(forms.Form):
    form_classes = []

    def __init__(self, *args, **kwargs):
        super(CombinedFormBase, self).__init__(*args, **kwargs)
        for f in self.form_classes:
            name = f.__name__.lower()
            setattr(self, name, f(*args, **kwargs))
            form = getattr(self, name)
            self.fields.update(form.fields)
            self.initial.update(form.initial)

    def is_valid(self):
        isValid = True
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            if not form.is_valid():
                isValid = False
        # is_valid will trigger clean method
        # so it should be called after all other forms is_valid are called
        # otherwise clean_data will be empty
        if not super(CombinedFormBase, self).is_valid() :
            isValid = False
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            self.errors.update(form.errors)
        return isValid

    def clean(self):
        cleaned_data = super(CombinedFormBase, self).clean()
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            cleaned_data.update(form.cleaned_data)
        return cleaned_data

用法示例:

class ConsumerRegistrationForm(CombinedFormBase):
    form_classes = [RegistrationForm, ConsumerProfileForm]

class RegisterView(FormView):
    template_name = "register.html"
    form_class = ConsumerRegistrationForm

    def form_valid(self, form):
        # some actions...
        return redirect(self.get_success_url())

You can try to use this pieces of code:

class CombinedFormBase(forms.Form):
    form_classes = []

    def __init__(self, *args, **kwargs):
        super(CombinedFormBase, self).__init__(*args, **kwargs)
        for f in self.form_classes:
            name = f.__name__.lower()
            setattr(self, name, f(*args, **kwargs))
            form = getattr(self, name)
            self.fields.update(form.fields)
            self.initial.update(form.initial)

    def is_valid(self):
        isValid = True
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            if not form.is_valid():
                isValid = False
        # is_valid will trigger clean method
        # so it should be called after all other forms is_valid are called
        # otherwise clean_data will be empty
        if not super(CombinedFormBase, self).is_valid() :
            isValid = False
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            self.errors.update(form.errors)
        return isValid

    def clean(self):
        cleaned_data = super(CombinedFormBase, self).clean()
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            cleaned_data.update(form.cleaned_data)
        return cleaned_data

Example Usage:

class ConsumerRegistrationForm(CombinedFormBase):
    form_classes = [RegistrationForm, ConsumerProfileForm]

class RegisterView(FormView):
    template_name = "register.html"
    form_class = ConsumerRegistrationForm

    def form_valid(self, form):
        # some actions...
        return redirect(self.get_success_url())

回答 2

我和erikbwork都有一个问题,即一个模型只能包含在一个通用的基于类的视图中。我找到了类似苗的类似方法,但是更加模块化。

我写了一个Mixin,因此您可以使用所有通用的基于类的视图。定义模型,字段,现在还定义child_model和child_field-然后可以将两个模型的字段包装在标签中,如Zach描述。

class ChildModelFormMixin: 
    ''' extends ModelFormMixin with the ability to include ChildModelForm '''
    child_model = ""
    child_fields = ()
    child_form_class = None

    def get_child_model(self):
        return self.child_model

    def get_child_fields(self):
        return self.child_fields

    def get_child_form(self):
        if not self.child_form_class:
            self.child_form_class = model_forms.modelform_factory(self.get_child_model(), fields=self.get_child_fields())
        return self.child_form_class(**self.get_form_kwargs())

    def get_context_data(self, **kwargs):
        if 'child_form' not in kwargs:
            kwargs['child_form'] = self.get_child_form()
        return super().get_context_data(**kwargs)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        child_form = self.get_child_form()

        # check if both forms are valid
        form_valid = form.is_valid()
        child_form_valid = child_form.is_valid()

        if form_valid and child_form_valid:
            return self.form_valid(form, child_form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, child_form):
        self.object = form.save()
        save_child_form = child_form.save(commit=False)
        save_child_form.course_key = self.object
        save_child_form.save()

        return HttpResponseRedirect(self.get_success_url())

用法示例:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_fields = ('payment_token', 'cart',)

或使用ModelFormClass:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_form_class = ConsumerProfileForm

做完了 希望能对某人有所帮助。

erikbwork and me both had the problem that one can only include one model into a generic Class Based View. I found a similar way of approaching it like Miao, but more modular.

I wrote a Mixin so you can use all generic Class Based Views. Define model, fields and now also child_model and child_field – and then you can wrap fields of both models in a tag like Zach describes.

class ChildModelFormMixin: 
    ''' extends ModelFormMixin with the ability to include ChildModelForm '''
    child_model = ""
    child_fields = ()
    child_form_class = None

    def get_child_model(self):
        return self.child_model

    def get_child_fields(self):
        return self.child_fields

    def get_child_form(self):
        if not self.child_form_class:
            self.child_form_class = model_forms.modelform_factory(self.get_child_model(), fields=self.get_child_fields())
        return self.child_form_class(**self.get_form_kwargs())

    def get_context_data(self, **kwargs):
        if 'child_form' not in kwargs:
            kwargs['child_form'] = self.get_child_form()
        return super().get_context_data(**kwargs)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        child_form = self.get_child_form()

        # check if both forms are valid
        form_valid = form.is_valid()
        child_form_valid = child_form.is_valid()

        if form_valid and child_form_valid:
            return self.form_valid(form, child_form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, child_form):
        self.object = form.save()
        save_child_form = child_form.save(commit=False)
        save_child_form.course_key = self.object
        save_child_form.save()

        return HttpResponseRedirect(self.get_success_url())

Example Usage:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_fields = ('payment_token', 'cart',)

Or with ModelFormClass:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_form_class = ConsumerProfileForm

Done. Hope that helps someone.


回答 3

您可能应该看一下Inline表单集。当模型通过外键关联时,将使用内联表单集。

You probably should take a look at Inline formsets. Inline formsets are used when your models are related by a foreign key.


回答 4

您可以在此处检查我的答案是否存在类似问题。

它讨论了如何将注册和用户配置文件合并为一种形式,但是可以将其推广到任何ModelForm组合。

You can check my answer here for a similar problem.

It talks about how to combine registration and user profile into one form, but it can be generalized to any ModelForm combination.


回答 5

我在项目中使用了django BetterformsMultiForm和MultiModelForm。但是,可以改进代码。例如,它依赖于django.six,而3. +不支持,但所有这些都可以轻松修复。

这个问题在StackOverflow中已经出现 几次了,所以我认为是时候找到一种标准的方法来解决这个问题了。

I used django betterforms‘s MultiForm and MultiModelForm in my project. The code can be improved, though. For example, it’s dependent on django.six, which isn’t supported by 3.+, but all of these can easily be fixed

This question has appeared several times in StackOverflow, so I think it’s time to find a standardized way of coping with this.


使用Django 1.7加载初始数据和数据迁移

问题:使用Django 1.7加载初始数据和数据迁移

我最近从Django 1.6切换到1.7,并且开始使用迁移功能(我从未使用过South)。

在1.7之前,我曾经用fixture/initial_data.json文件加载初始数据,该文件是用python manage.py syncdb命令加载的(在创建数据库时)。

现在,我开始使用迁移,并且不赞成使用此行为:

如果应用程序使用迁移,则不会自动加载固定装置。由于Django 2.0中的应用程序需要迁移,因此该行为被视为已弃用。如果要加载应用程序的初始数据,请考虑在数据迁移中进行。(https://docs.djangoproject.com/zh-CN/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures

官方文件并没有对如何做一个明显的例子,所以我的问题是:

使用数据迁移导入此类初始数据的最佳方法是什么:

  1. 通过多次调用编写Python代码mymodel.create(...)
  2. 使用或编写Django函数(如调用loaddata)从JSON固定文件加载数据。

我更喜欢第二种选择。

我不想使用South,因为Django现在似乎可以本地使用。

I recently switched from Django 1.6 to 1.7, and I began using migrations (I never used South).

Before 1.7, I used to load initial data with a fixture/initial_data.json file, which was loaded with the python manage.py syncdb command (when creating the database).

Now, I started using migrations, and this behavior is deprecated :

If an application uses migrations, there is no automatic loading of fixtures. Since migrations will be required for applications in Django 2.0, this behavior is considered deprecated. If you want to load initial data for an app, consider doing it in a data migration. (https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures)

The official documentation does not have a clear example on how to do it, so my question is :

What is the best way to import such initial data using data migrations :

  1. Write Python code with multiple calls to mymodel.create(...),
  2. Use or write a Django function (like calling loaddata) to load data from a JSON fixture file.

I prefer the second option.

I don’t want to use South, as Django seems to be able to do it natively now.


回答 0

更新:有关此解决方案可能导致的问题,请参见下面的@GwynBleidD注释,有关对将来的模型更改更持久的方法,请参见下面的@Rockallite答案。


假设您有一个夹具文件 <yourapp>/fixtures/initial_data.json

  1. 创建您的空迁移:

    在Django 1.7中:

    python manage.py makemigrations --empty <yourapp>

    在Django 1.8+中,您可以提供一个名称:

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
  2. 编辑您的迁移文件 <yourapp>/migrations/0002_auto_xxx.py

    2.1。自定义实现,受Django’ loaddata(初始答案)启发:

    import os
    from sys import path
    from django.core import serializers
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
    
        fixture = open(fixture_file, 'rb')
        objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
        for obj in objects:
            obj.save()
        fixture.close()
    
    def unload_fixture(apps, schema_editor):
        "Brutally deleting all entries for this model..."
    
        MyModel = apps.get_model("yourapp", "ModelName")
        MyModel.objects.all().delete()
    
    class Migration(migrations.Migration):  
    
        dependencies = [
            ('yourapp', '0001_initial'),
        ]
    
        operations = [
            migrations.RunPython(load_fixture, reverse_code=unload_fixture),
        ]

    2.2。一个更简单的解决方案load_fixture(根据@juliocesar的建议):

    from django.core.management import call_command
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
        call_command('loaddata', fixture_file) 

    如果要使用自定义目录,则很有用。

    2.3。最简单的:调用loaddataapp_label从将加载器具<yourapp>fixtures目录自动:

    from django.core.management import call_command
    
    fixture = 'initial_data'
    
    def load_fixture(apps, schema_editor):
        call_command('loaddata', fixture, app_label='yourapp') 

    如果您未指定app_label,loaddata会尝试fixture所有应用程序的夹具目录(您可能不想要)中加载文件名。

  3. 运行

    python manage.py migrate <yourapp>

Update: See @GwynBleidD’s comment below for the problems this solution can cause, and see @Rockallite’s answer below for an approach that’s more durable to future model changes.


Assuming you have a fixture file in <yourapp>/fixtures/initial_data.json

  1. Create your empty migration:

    In Django 1.7:

    python manage.py makemigrations --empty <yourapp>
    

    In Django 1.8+, you can provide a name:

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
    
  2. Edit your migration file <yourapp>/migrations/0002_auto_xxx.py

    2.1. Custom implementation, inspired by Django’ loaddata (initial answer):

    import os
    from sys import path
    from django.core import serializers
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
    
        fixture = open(fixture_file, 'rb')
        objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
        for obj in objects:
            obj.save()
        fixture.close()
    
    def unload_fixture(apps, schema_editor):
        "Brutally deleting all entries for this model..."
    
        MyModel = apps.get_model("yourapp", "ModelName")
        MyModel.objects.all().delete()
    
    class Migration(migrations.Migration):  
    
        dependencies = [
            ('yourapp', '0001_initial'),
        ]
    
        operations = [
            migrations.RunPython(load_fixture, reverse_code=unload_fixture),
        ]
    

    2.2. A simpler solution for load_fixture (per @juliocesar’s suggestion):

    from django.core.management import call_command
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
        call_command('loaddata', fixture_file) 
    

    Useful if you want to use a custom directory.

    2.3. Simplest: calling loaddata with app_label will load fixtures from the <yourapp>‘s fixtures dir automatically :

    from django.core.management import call_command
    
    fixture = 'initial_data'
    
    def load_fixture(apps, schema_editor):
        call_command('loaddata', fixture, app_label='yourapp') 
    

    If you don’t specify app_label, loaddata will try to load fixture filename from all apps fixtures directories (which you probably don’t want).

  3. Run it

    python manage.py migrate <yourapp>
    

回答 1

精简版

您应使用loaddata的数据迁移直接管理命令。

# Bad example for a data migration
from django.db import migrations
from django.core.management import call_command


def load_fixture(apps, schema_editor):
    # No, it's wrong. DON'T DO THIS!
    call_command('loaddata', 'your_data.json', app_label='yourapp')


class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(load_fixture),
    ]

长版

loaddata利用利用django.core.serializers.python.Deserializer最新模型反序列化迁移中的历史数据。那是不正确的行为。

例如,假设有一个数据迁移,该数据迁移利用loaddata管理命令从固定装置加载数据,并且该数据迁移已应用于您的开发环境。

以后,您决定将新的必填字段添加到相应的模型中,这样就可以对更新后的模型进行新迁移(并可能在./manage.py makemigrations提示您时向新字段提供一次性值)。

您运行下一个迁移,一切顺利。

最后,开发完Django应用程序,然后将其部署在生产服务器上。现在是时候在生产环境上从头开始运行整个迁移了。

但是,数据迁移失败。这是因为来自loaddata命令的反序列化模型(代表当前代码)无法与添加的新必填字段的空数据一起保存。原始灯具缺少必要的数据!

但是,即使使用新字段所需的数据更新了灯具,数据迁移仍然会失败。在运行数据迁移时,尚未应用将相应列添加到数据库的下一次迁移。您无法将数据保存到不存在的列中!

结论:在数据迁移中,该loaddata命令引入了模型与数据库之间潜在的不一致。您绝对应该在数据迁移中直接使用它。

解决方案

loaddata命令依赖于django.core.serializers.python._get_model功能以从固定装置中获取相应的模型,该装置将返回模型的最新版本。我们需要对其进行Monkey修补,以便获得历史模型。

(以下代码适用于Django 1.8.x)

# Good example for a data migration
from django.db import migrations
from django.core.serializers import base, python
from django.core.management import call_command


def load_fixture(apps, schema_editor):
    # Save the old _get_model() function
    old_get_model = python._get_model

    # Define new _get_model() function here, which utilizes the apps argument to
    # get the historical version of a model. This piece of code is directly stolen
    # from django.core.serializers.python._get_model, unchanged. However, here it
    # has a different context, specifically, the apps variable.
    def _get_model(model_identifier):
        try:
            return apps.get_model(model_identifier)
        except (LookupError, TypeError):
            raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)

    # Replace the _get_model() function on the module, so loaddata can utilize it.
    python._get_model = _get_model

    try:
        # Call loaddata command
        call_command('loaddata', 'your_data.json', app_label='yourapp')
    finally:
        # Restore old _get_model() function
        python._get_model = old_get_model


class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(load_fixture),
    ]

Short version

You should NOT use loaddata management command directly in a data migration.

# Bad example for a data migration
from django.db import migrations
from django.core.management import call_command


def load_fixture(apps, schema_editor):
    # No, it's wrong. DON'T DO THIS!
    call_command('loaddata', 'your_data.json', app_label='yourapp')


class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(load_fixture),
    ]

Long version

loaddata utilizes django.core.serializers.python.Deserializer which uses the most up-to-date models to deserialize historical data in a migration. That’s incorrect behavior.

For example, supposed that there is a data migration which utilizes loaddata management command to load data from a fixture, and it’s already applied on your development environment.

Later, you decide to add a new required field to the corresponding model, so you do it and make a new migration against your updated model (and possibly provide a one-off value to the new field when ./manage.py makemigrations prompts you).

You run the next migration, and all is well.

Finally, you’re done developing your Django application, and you deploy it on the production server. Now it’s time for you to run the whole migrations from scratch on the production environment.

However, the data migration fails. That’s because the deserialized model from loaddata command, which represents the current code, can’t be saved with empty data for the new required field you added. The original fixture lacks necessary data for it!

But even if you update the fixture with required data for the new field, the data migration still fails. When the data migration is running, the next migration which adds the corresponding column to the database, is not applied yet. You can’t save data to a column which does not exist!

Conclusion: in a data migration, the loaddata command introduces potential inconsistency between the model and the database. You should definitely NOT use it directly in a data migration.

The Solution

loaddata command relies on django.core.serializers.python._get_model function to get the corresponding model from a fixture, which will return the most up-to-date version of a model. We need to monkey-patch it so it gets the historical model.

(The following code works for Django 1.8.x)

# Good example for a data migration
from django.db import migrations
from django.core.serializers import base, python
from django.core.management import call_command


def load_fixture(apps, schema_editor):
    # Save the old _get_model() function
    old_get_model = python._get_model

    # Define new _get_model() function here, which utilizes the apps argument to
    # get the historical version of a model. This piece of code is directly stolen
    # from django.core.serializers.python._get_model, unchanged. However, here it
    # has a different context, specifically, the apps variable.
    def _get_model(model_identifier):
        try:
            return apps.get_model(model_identifier)
        except (LookupError, TypeError):
            raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)

    # Replace the _get_model() function on the module, so loaddata can utilize it.
    python._get_model = _get_model

    try:
        # Call loaddata command
        call_command('loaddata', 'your_data.json', app_label='yourapp')
    finally:
        # Restore old _get_model() function
        python._get_model = old_get_model


class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(load_fixture),
    ]

回答 2

受一些评论(即n__o的评论)的启发,以及我initial_data.*在多个应用程序中散布了许多文件这一事实,我决定创建一个Django应用程序,以方便创建这些数据迁移。

使用Django的迁移夹具,你可以简单地运行下面的管理命令,它会通过所有搜索你INSTALLED_APPSinitial_data.*文件,并把它们变成数据迁移。

./manage.py create_initial_data_fixtures
Migrations for 'eggs':
  0002_auto_20150107_0817.py:
Migrations for 'sausage':
  Ignoring 'initial_data.yaml' - migration already exists.
Migrations for 'foo':
  Ignoring 'initial_data.yaml' - not migrated.

看到 安装/使用说明, django-migration-fixture

Inspired by some of the comments (namely n__o’s) and the fact that I have a lot of initial_data.* files spread out over multiple apps I decided to create a Django app that would facilitate the creation of these data migrations.

Using django-migration-fixture you can simply run the following management command and it will search through all your INSTALLED_APPS for initial_data.* files and turn them into data migrations.

./manage.py create_initial_data_fixtures
Migrations for 'eggs':
  0002_auto_20150107_0817.py:
Migrations for 'sausage':
  Ignoring 'initial_data.yaml' - migration already exists.
Migrations for 'foo':
  Ignoring 'initial_data.yaml' - not migrated.

See django-migration-fixture for install/usage instructions.


回答 3

为了给您的数据库一些初始数据,编写一个数据迁移。 在数据迁移中,使用RunPython函数加载数据。

不要编写任何loaddata命令,因为这种方式已被弃用。

您的数据迁移将仅运行一次。迁移是迁移的有序序列。运行003_xxxx.py迁移时,django迁移会在数据库中写入该应用已迁移到该版本(003)的信息,并将仅运行以下迁移。

In order to give your database some initial data, write a data migration. In the data migration, use the RunPython function to load your data.

Don’t write any loaddata command as this way is deprecated.

Your data migrations will be run only once. The migrations are an ordered sequence of migrations. When the 003_xxxx.py migrations is run, django migrations writes in the database that this app is migrated until this one (003), and will run the following migrations only.


回答 4

不幸的是,上面介绍的解决方案对我不起作用。我发现每次更改模型时都必须更新固定装置。理想情况下,我会写数据迁移来类似地修改创建的数据和夹具加载的数据。

为了方便起见,我编写了一个快速功能,它将在fixtures当前应用程序的目录中查找并加载夹具。将此功能放入与迁移中的字段匹配的模型历史记录中的迁移中。

The solutions presented above didn’t work for me unfortunately. I found that every time I change my models I have to update my fixtures. Ideally I would instead write data migrations to modify created data and fixture-loaded data similarly.

To facilitate this I wrote a quick function which will look in the fixtures directory of the current app and load a fixture. Put this function into a migration in the point of the model history that matches the fields in the migration.


回答 5

我认为固定装置有点不好。如果您的数据库经常更改,那么使其保持最新状态将很快成为噩梦。实际上,这不仅是我的观点,在《 Django的两个独家报道》一书中,它的解释要好得多。

相反,我将编写一个Python文件来提供初始设置。如果您还需要其他东西,我建议您去看看工厂男孩

如果需要迁移某些数据,则应使用数据迁移

还有关于使用固定装置的“燃烧固定装置,使用模型工厂”

In my opinion fixtures are a bit bad. If your database changes frequently, keeping them up-to-date will came a nightmare soon. Actually, it’s not only my opinion, in the book “Two Scoops of Django” it’s explained much better.

Instead I’ll write a Python file to provide initial setup. If you need something more I’ll suggest you look at Factory boy.

If you need to migrate some data you should use data migrations.

There’s also “Burn Your Fixtures, Use Model Factories” about using fixtures.


回答 6

在Django 2.1上,我想用初始数据加载某些模型(例如国家名称)。

但是我希望这种情况在执行初始迁移后立即自动发生。

因此,我认为拥有一个 sql/在每个应用程序中需要加载初始数据文件夹。

然后,在该sql/文件夹中,我将包含.sql带有所需DML的文件,以将初始数据加载到相应的模型中,例如:

INSERT INTO appName_modelName(fieldName)
VALUES
    ("country 1"),
    ("country 2"),
    ("country 3"),
    ("country 4");

为了更具描述性,这是包含sql/文件夹的应用程序的外观:

另外,我发现某些情况下需要按sql特定顺序执行脚本。因此,我决定为文件名加上一个连续的数字,如上图所示。

然后,我需要一种方法,SQLs可以自动在任何应用程序文件夹中加载可用的文件python manage.py migrate

因此,我创建了另一个名为的应用程序initial_data_migrations,然后将该应用程序添加到INSTALLED_APPSin settings.py文件列表中。然后,我在migrations里面创建了一个文件夹,并添加了一个名为run_sql_scripts.py实际上是自定义迁移)的文件。如下图所示:

我创建run_sql_scripts.py了它,以便它负责运行sql每个应用程序中可用的所有脚本。然后当有人跑步时将其解雇python manage.py migrate。此自定义migration还会将涉及的应用程序添加为依赖项,这样,它sql仅在所需的应用程序执行了0001_initial.py迁移之后才尝试运行语句(我们不想尝试针对不存在的表运行SQL语句)。

这是该脚本的来源:

import os
import itertools

from django.db import migrations
from YourDjangoProjectName.settings import BASE_DIR, INSTALLED_APPS

SQL_FOLDER = "/sql/"

APP_SQL_FOLDERS = [
    (os.path.join(BASE_DIR, app + SQL_FOLDER), app) for app in INSTALLED_APPS
    if os.path.isdir(os.path.join(BASE_DIR, app + SQL_FOLDER))
]

SQL_FILES = [
    sorted([path + file for file in os.listdir(path) if file.lower().endswith('.sql')])
    for path, app in APP_SQL_FOLDERS
]


def load_file(path):
    with open(path, 'r') as f:
        return f.read()


class Migration(migrations.Migration):

    dependencies = [
        (app, '__first__') for path, app in APP_SQL_FOLDERS
    ]

    operations = [
        migrations.RunSQL(load_file(f)) for f in list(itertools.chain.from_iterable(SQL_FILES))
    ]

我希望有人觉得这有帮助,对我来说效果很好!如果您有任何疑问,请告诉我。

注意:这可能不是最好的解决方案,因为我刚刚开始使用django,但是由于我在使用django进行搜索时没有找到太多信息,因此仍想与大家共享此“操作方法”。

On Django 2.1, I wanted to load some models (Like country names for example) with initial data.

But I wanted this to happen automatically right after the execution of initial migrations.

So I thought that it would be great to have an sql/ folder inside each application that required initial data to be loaded.

Then within that sql/ folder I would have .sql files with the required DMLs to load the initial data into the corresponding models, for example:

INSERT INTO appName_modelName(fieldName)
VALUES
    ("country 1"),
    ("country 2"),
    ("country 3"),
    ("country 4");

To be more descriptive, this is how an app containing an sql/ folder would look:

Also I found some cases where I needed the sql scripts to be executed in a specific order. So I decided to prefix the file names with a consecutive number as seen in the image above.

Then I needed a way to load any SQLs available inside any application folder automatically by doing python manage.py migrate.

So I created another application named initial_data_migrations and then I added this app to the list of INSTALLED_APPS in settings.py file. Then I created a migrations folder inside and added a file called run_sql_scripts.py (Which actually is a custom migration). As seen in the image below:

I created run_sql_scripts.py so that it takes care of running all sql scripts available within each application. This one is then fired when someone runs python manage.py migrate. This custom migration also adds the involved applications as dependencies, that way it attempts to run the sql statements only after the required applications have executed their 0001_initial.py migrations (We don’t want to attempt running a SQL statement against a non-existent table).

Here is the source of that script:

import os
import itertools

from django.db import migrations
from YourDjangoProjectName.settings import BASE_DIR, INSTALLED_APPS

SQL_FOLDER = "/sql/"

APP_SQL_FOLDERS = [
    (os.path.join(BASE_DIR, app + SQL_FOLDER), app) for app in INSTALLED_APPS
    if os.path.isdir(os.path.join(BASE_DIR, app + SQL_FOLDER))
]

SQL_FILES = [
    sorted([path + file for file in os.listdir(path) if file.lower().endswith('.sql')])
    for path, app in APP_SQL_FOLDERS
]


def load_file(path):
    with open(path, 'r') as f:
        return f.read()


class Migration(migrations.Migration):

    dependencies = [
        (app, '__first__') for path, app in APP_SQL_FOLDERS
    ]

    operations = [
        migrations.RunSQL(load_file(f)) for f in list(itertools.chain.from_iterable(SQL_FILES))
    ]

I hope someone finds this helpful, it worked just fine for me!. If you have any questions please let me know.

NOTE: This might not be the best solution since I’m just getting started with django, however still wanted to share this “How-to” with you all since I didn’t find much information while googling about this.


如何在Django中使用redis?

问题:如何在Django中使用redis?

我听说过redis-cache,但是它是如何工作的呢?通过以某种方式缓存rdbms查询,它是否被用作Django和我的rdbms之间的一层?

还是应该将其直接用作数据库?我对此表示怀疑,因为该github页面不包含任何登录详细信息,所以没有设置..只是告诉您设置一些config属性。

I’ve heard of redis-cache but how exactly does it work? Is it used as a layer between django and my rdbms, by caching the rdbms queries somehow?

Or is it supposed to be used directly as the database? Which I doubt, since that github page doesn’t cover any login details, no setup.. just tells you to set some config property.


回答 0

这个Redis的Python模块在自述文件中有一个清晰的用法示例:http : //github.com/andymccurdy/redis-py

Redis被设计为RAM缓存。它支持键的基本GET和SET以及字典等集合的存储。您可以通过将RDBMS查询的输出存储在Redis中来缓存它们。目标是加快Django网站的速度。在需要速度之前,不要开始使用Redis或任何其他缓存-不要过早地进行优化。

This Python module for Redis has a clear usage example in the readme: http://github.com/andymccurdy/redis-py

Redis is designed to be a RAM cache. It supports basic GET and SET of keys plus the storing of collections such as dictionaries. You can cache RDBMS queries by storing their output in Redis. The goal would be to speed up your Django site. Don’t start using Redis or any other cache until you need the speed – don’t prematurely optimize.


回答 1

仅仅因为Redis将事物存储在内存中并不意味着它就是缓存。我见过人们将它用作数据的持久存储。

可以将其用作高速缓存暗示它可以用作高性能存储。如果您的Redis系统出现故障,则可能会丢失尚未再次写回到磁盘的数据。有一些方法可以减轻这种危险,例如,热备份副本。如果您的数据是“关键任务”,例如您经营一家银行或商店,那么Redis可能不是您的最佳选择。但是,如果您使用持久的实时数据或一些社交互动的东西来编写流量高的游戏,并且将数据丢失的可能性控制在可以接受的范围内,那么Redis可能值得一看。

无论如何,重点仍然存在,是的,Redis可以用作数据库。

Just because Redis stores things in-memory does not mean that it is meant to be a cache. I have seen people using it as a persistent store for data.

That it can be used as a cache is a hint that it is useful as a high-performance storage. If your Redis system goes down though you might loose data that was not been written back onto the disk again. There are some ways to mitigate such dangers, e.g. a hot-standby replica. If your data is ‘mission-critical’, like if you run a bank or a shop, Redis might not be the best pick for you. But if you write a high-traffic game with persistent live data or some social-interaction stuff and manage the probability of data-loss to be quite acceptable, then Redis might be worth a look.

Anyway, the point remains, yes, Redis can be used as a database.


回答 2

Redis基本上是一个“记忆中”的KV存储,带有大量的铃声和口哨声。它非常灵活。您可以将其用作临时存储(例如缓存)或永久存储(例如数据库)(带有其他答案中提到的警告)。

当与Django结合使用时,Redis的最佳/最常见用例可能是缓存“响应”和会话。

这里有一个后端https://github.com/sebleier/django-redis-cache/和Django文档中的出色文档,这里是https://docs.djangoproject.com/en/1.3/topics/cache/

我最近开始使用https://github.com/erussell/django-redis-status监视我的缓存-很有魅力。(在redis上配置maxmemory或结果不是很有用)。

Redis is basically an ‘in memory’ KV store with loads of bells and whistles. It is extremely flexible. You can use it as a temporary store, like a cache, or a permanent store, like a database (with caveats as mentioned in other answers).

When combined with Django the best/most common use case for Redis is probably to cache ‘responses’ and sessions.

There’s a backend here https://github.com/sebleier/django-redis-cache/ and excellent documentation in the Django docs here: https://docs.djangoproject.com/en/1.3/topics/cache/ .

I’ve recently started using https://github.com/erussell/django-redis-status to monitor my cache – works a charm. (Configure maxmemory on redis or the results aren’t so very useful).


回答 3

您还可以将Redis用作Django应用程序中分布式任务的队列。您可以将其用作CeleryPython RQ的消息代理。

You can also use Redis as a queue for distributed tasks in your Django app. You can use it as a message broker for Celery or Python RQ.


回答 4

Redis作为主数据库

是的,您可以将Redis键值存储用作主数据库。Redis不仅存储键值对,还支持不同的数据结构,例如

  1. 清单
  2. 排序集
  3. 散列
  4. 位图
  5. 超级日志

Redis数据类型官方文档

Redis在内存键值存储中,因此如果Redis服务器发生故障,您必须知道它,您的数据将丢失。

Redis还可以保留数据检查官方文档。

Redis Persistence官方文档


Redis作为缓存

是的,Redis驻留在Django和RDBMS之间。

这个怎么运作

given a URL, try finding that page in the cache if the page is in the cache: return the cached page else: generate the page save the generated page in the cache (for next time) return the generated page

Django的缓存框架Official Doc


如何在Django中使用Redis

我们可以将redis python客户端redis-py用于Django应用程序。

Redis Python客户端redis-py Github

我们可以将Django-redis用于Django缓存后端。

Django-redis基于redis-py构建,并添加了与Django应用程序相关的额外功能。

Django-redis doc Github

还存在其他库。


Redis用例和数据类型

一些用例

  • 会话缓存
  • 实时分析
  • Web缓存
  • 排行榜

按核心数据结构类型划分的顶级Redis用例


使用Redis的大型科技公司

 Twitter GitHub Weibo Pinterest Snapchat Craigslist Digg StackOverflow Flickr 

Redis as a Primary database

Yes you can use Redis key-value store as a primary database. Redis not only store key-value pairs it also support different data structures like

  1. List
  2. Set
  3. Sorted set
  4. Hashes
  5. Bitmaps
  6. Hyperloglogs

Redis Data types Official doc

Redis is in memory key-value store so you must aware of it if Redis server failure occurred your data will be lost.

Redis can also persist data check official doc.

Redis Persistence Official doc


Redis as a Cache

Yes Redis reside between in Django and RDBMS.

How it works

given a URL, try finding that page in the cache if the page is in the cache: return the cached page else: generate the page save the generated page in the cache (for next time) return the generated page

Django’s cache framework Official Doc


How can use Redis with Django

We can use redis python client redis-py for Django application.

Redis python client redis-py Github

We can use Django-redis for django cache backend.

Django-redis build on redis-py and added extra features related to django application.

Django-redis doc Github

Other libraries also exists.


Redis use cases and data types

Some use cases

  • Session cache
  • Real time analytics
  • Web caching
  • Leaderboards

Top Redis Use Cases by Core Data structure types


Big Tech companies using Redis

 Twitter GitHub Weibo Pinterest Snapchat Craigslist Digg StackOverflow Flickr 


按属性筛选

问题:按属性筛选

是否可以通过模型属性过滤Django查询集?

我的模型中有一个方法:

@property
def myproperty(self):
    [..]

现在我想按此属性进行过滤,例如:

MyModel.objects.filter(myproperty=[..])

这有可能吗?

Is it possible to filter a Django queryset by model property?

i have a method in my model:

@property
def myproperty(self):
    [..]

and now i want to filter by this property like:

MyModel.objects.filter(myproperty=[..])

is this somehow possible?


回答 0

不。Django过滤器在数据库级别运行,生成SQL。要基于Python属性进行过滤,您必须将对象加载到Python中以评估该属性-到那时,您已经完成了加载该对象的所有工作。

Nope. Django filters operate at the database level, generating SQL. To filter based on Python properties, you have to load the object into Python to evaluate the property–and at that point, you’ve already done all the work to load it.


回答 1

我可能会误解您的原始问题,但是python中内置了一个过滤器

filtered = filter(myproperty, MyModel.objects)

但是最好使用列表理解

filtered = [x for x in MyModel.objects if x.myproperty()]

甚至更好的是生成器表达式

filtered = (x for x in MyModel.objects if x.myproperty())

I might be misunderstanding your original question, but there is a filter builtin in python.

filtered = filter(myproperty, MyModel.objects)

But it’s better to use a list comprehension:

filtered = [x for x in MyModel.objects if x.myproperty()]

or even better, a generator expression:

filtered = (x for x in MyModel.objects if x.myproperty())

回答 2

摆脱@TheGrimmScientist建议的解决方法,您可以通过在Manager或QuerySet上定义这些“ sql属性”,然后重新使用/链接/组成它们,来制成这些“ sql属性”:

与经理一起:

class CompanyManager(models.Manager):
    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyManager()

Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)

使用QuerySet:

class CompanyQuerySet(models.QuerySet):
    def many_employees(self, n=50):
        return self.filter(num_employees__gte=n)

    def needs_fewer_chairs_than(self, n=5):
        return self.with_chairs_needed().filter(chairs_needed__lt=n)

    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyQuerySet.as_manager()

Company.objects.needs_fewer_chairs_than(4).many_employees()

有关更多信息,请参见https://docs.djangoproject.com/en/1.9/topics/db/managers/。请注意,我将关闭文档,并且尚未测试以上内容。

Riffing off @TheGrimmScientist’s suggested workaround, you can make these “sql properties” by defining them on the Manager or the QuerySet, and reuse/chain/compose them:

With a Manager:

class CompanyManager(models.Manager):
    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyManager()

Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)

With a QuerySet:

class CompanyQuerySet(models.QuerySet):
    def many_employees(self, n=50):
        return self.filter(num_employees__gte=n)

    def needs_fewer_chairs_than(self, n=5):
        return self.with_chairs_needed().filter(chairs_needed__lt=n)

    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyQuerySet.as_manager()

Company.objects.needs_fewer_chairs_than(4).many_employees()

See https://docs.djangoproject.com/en/1.9/topics/db/managers/ for more. Note that I am going off the documentation and have not tested the above.


回答 3

看起来将F()与批注一起使用将是我的解决方案。

它不会被过滤@property,因为F在将对象带入python之前会与数据库进行对话。但是仍然把它作为答案,因为我想要按属性过滤的原因实际上是希望通过对两个不同字段进行简单算术运算的结果来过滤对象。

因此,类似以下内容:

companies = Company.objects\
    .annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
    .filter(chairs_needed__lt=4)

而不是将属性定义为:

@property
def chairs_needed(self):
    return self.num_employees - self.num_chairs

然后对所有对象进行列表理解。

Looks like using F() with annotations will be my solution to this.

It’s not going to filter by @property, since F talks to the databse before objects are brought into python. But still putting it here as an answer since my reason for wanting filter by property was really wanting to filter objects by the result of simple arithmetic on two different fields.

so, something along the lines of:

companies = Company.objects\
    .annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
    .filter(chairs_needed__lt=4)

rather than defining the property to be:

@property
def chairs_needed(self):
    return self.num_employees - self.num_chairs

then doing a list comprehension across all objects.


回答 4

我遇到了同样的问题,并开发了这个简单的解决方案:

objects_id = [x.id for x in MyModel.objects.all() if x.myProperty == [...]]
MyModel.objects.filter(id__in=objects_id)

我知道这不是最有效的解决方案,但在像我这样的简单情况下可能会有所帮助

I had the same problem, and I developed this simple solution:

objects_id = [x.id for x in MyModel.objects.all() if x.myProperty == [...]]
MyModel.objects.filter(id__in=objects_id)

I know it’s not the most performatic solution, but may help in simple cases as mine


回答 5

请有人纠正我,但我想至少在我自己的情况下,我已经找到了解决方案。

我想处理所有属性完全等于…的元素。

但是我有几个模型,这个例程应该适用于所有模型。它确实:

def selectByProperties(modelType, specify):
    clause = "SELECT * from %s" % modelType._meta.db_table

    if len(specify) > 0:
        clause += " WHERE "
        for field, eqvalue in specify.items():
            clause += "%s = '%s' AND " % (field, eqvalue)
        clause = clause [:-5]  # remove last AND

    print clause
    return modelType.objects.raw(clause)

通过这个通用子例程,我可以选择与我的“ specify”(属性名称,属性值)组合的字典完全相等的所有元素。

第一个参数采用(models.Model),

第二个字典,例如:{“ property1”:“ 77”,“ property2”:“ 12”}

它创建了一条SQL语句,例如

SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'

并在这些元素上返回QuerySet。

这是一个测试功能:

from myApp.models import myModel

def testSelectByProperties ():

    specify = {"property1" : "77" , "property2" : "12"}
    subset = selectByProperties(myModel, specify)

    nameField = "property0"
    ## checking if that is what I expected:
    for i in subset:
        print i.__dict__[nameField], 
        for j in specify.keys():
             print i.__dict__[j], 
        print 

和?你怎么看?

PLEASE someone correct me, but I guess I have found a solution, at least for my own case.

I want to work on all those elements whose properties are exactly equal to … whatever.

But I have several models, and this routine should work for all models. And it does:

def selectByProperties(modelType, specify):
    clause = "SELECT * from %s" % modelType._meta.db_table

    if len(specify) > 0:
        clause += " WHERE "
        for field, eqvalue in specify.items():
            clause += "%s = '%s' AND " % (field, eqvalue)
        clause = clause [:-5]  # remove last AND

    print clause
    return modelType.objects.raw(clause)

With this universal subroutine, I can select all those elements which exactly equal my dictionary of ‘specify’ (propertyname,propertyvalue) combinations.

The first parameter takes a (models.Model),

the second a dictionary like: {“property1” : “77” , “property2” : “12”}

And it creates an SQL statement like

SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'

and returns a QuerySet on those elements.

This is a test function:

from myApp.models import myModel

def testSelectByProperties ():

    specify = {"property1" : "77" , "property2" : "12"}
    subset = selectByProperties(myModel, specify)

    nameField = "property0"
    ## checking if that is what I expected:
    for i in subset:
        print i.__dict__[nameField], 
        for j in specify.keys():
             print i.__dict__[j], 
        print 

And? What do you think?


回答 6

我知道这是一个古老的问题,但是对于那些跳到这里的人来说,我认为阅读下面的问题和相对答案很有用:

如何在Django 1.4中自定义管理过滤器

i know it is an old question, but for the sake of those jumping here i think it is useful to read the question below and the relative answer:

How to customize admin filter in Django 1.4


回答 7

它也可能通过使用查询集批注复制属性get / set-逻辑,建议如@rattray@thegrimmscientist会同property。这可能会产生在Python级别数据库级别都可以使用的东西。

但是,不确定其缺点:请参阅此SO问题作为示例。

It may also be possible to use queryset annotations that duplicate the property get/set-logic, as suggested e.g. by @rattray and @thegrimmscientist, in conjunction with the property. This could yield something that works both on the Python level and on the database level.

Not sure about the drawbacks, however: see this SO question for an example.


如何打包python应用程序使其可点子安装?

问题:如何打包python应用程序使其可点子安装?

我在业余时间正在编写django应用程序,以参加我们正在上班的小费竞赛。我认为我会明智地使用这段时间,并逐步了解virtualenv,pip,打包,django 1.3以及如何编写易于重新分发的应用程序。到目前为止,一切都很好。

我要负责包装部分。例如,GitHub上的许多django应用程序大多(大致)以相同的方式捆绑在一起。我将以django-uni-forms为例。

我所做的一个假设是,MANIFEST.insetup.py是pip完成工作所需的唯一必需的部分。那是对的吗?如果我的假设是错误的,还需要哪些其他组件?

是否通常生成了所需的包装文件,或者是手工制作的?可以描述依赖项然后再安装吗?我的应用程序依赖django-uni-forms,并且在requirements.txt我用来安装依赖项的应用程序中的文件中列出了它;但这是包装系统可以解决的问题吗?

我需要遵循哪些步骤来打包我的应用程序,以使pip能够安装它和任何依赖项?

I’m writing a django application in my spare time for a footy-tipping competition we’re running at work. I figured I’d use this time wisely, and get up to speed on virtualenv, pip, packaging, django 1.3, and how to write an easily redistributable application. So far, so good.

I’m up to the packaging part. A lot of the django apps on GitHub for instance are mostly bundled (roughly) the same way. I’ll use django-uni-forms as an example.

An assumption I’m making is that the MANIFEST.in and setup.py are the only required pieces that pip needs to do its job. Is that correct? What other components are necessary if my assumption is wrong?

Are the required packaging files generally generated, or are they crafted by hand? Can dependencies be described and then installed also? My application depends on django-uni-forms, and I have it listed in a requirements.txt file within my app which I used to install the dependency; but is that something that the packaging system can take care of?

What are the steps I need to follow to package my application in such a way that pip will be able to install it and any dependencies?


回答 0

是的,MANIFEST.in而且setup.py应该是足够的。

这篇博客文章确实具有有关此主题的一些很好的信息: 打包Django可重用应用程序

这是另一个很好的详细概述,对我有很大帮助: Python打包用户指南

尤其是包含静态文件(模板)的提示很重要,因为起初可能并不明显。

是的,您可以在setup.py其中指定所需的软件包,这些软件包在安装应用程序时会自动获取。

例如:

    install_requires = [
        'django-profiles',
        'django-uni-forms',
    ],

显然,现在我们在两个地方定义了依赖项,但这并不一定意味着这些信息是重复的:setup.py与requirements.txt

通过此设置,您的软件包应可通过安装pip


正如Pierre在评论中指出的那样,Django的官方文档中现在还有一个相关的部分: 打包您的应用

然后是这个“完全不完整”的指南,它确实很好地概述了打包并将程序包上载到PyPI:分享您的爱的劳动:PyPI的快速与肮脏

Yes, MANIFEST.in and setup.py should be sufficient.

This blog post really has some good information on this topic: Packaging a Django reusable app

And here’s another good, detailed overview that helped me a lot: Python Packaging User Guide

Especially the tips to get your static files (templates) included are important as this might not be obvious at first.

And yes, you can specify required packages in your setup.py which are automatically fetched when installing your app.

For example:

    install_requires = [
        'django-profiles',
        'django-uni-forms',
    ],

Obviously now we have two places where dependencies are defined, but that doesn’t necessarily mean that these information are duplicated: setup.py vs requirements.txt

With this setup your package should be installable via pip.


As Pierre noted in the comments, there’s now also a relevant section in Django’s official documentation: Packaging your app

And then there is this “completely incomplete” guide, which really gives a great overview over packaging and uploading a package to PyPI: Sharing Your Labor of Love: PyPI Quick And Dirty


Django基于类的视图:如何将其他参数传递给as_view方法?

问题:Django基于类的视图:如何将其他参数传递给as_view方法?

我有一个基于类的自定义视图

# myapp/views.py
from django.views.generic import *

class MyView(DetailView):
    template_name = 'detail.html'
    model = MyModel

    def get_object(self, queryset=None):
        return queryset.get(slug=self.slug)

我想像这样传递slug参数(或其他参数到视图)

MyView.as_view(slug='hello_world')

我是否需要重写任何方法才能做到这一点?

I have a custom class-based view

# myapp/views.py
from django.views.generic import *

class MyView(DetailView):
    template_name = 'detail.html'
    model = MyModel

    def get_object(self, queryset=None):
        return queryset.get(slug=self.slug)

I want to pass in the slug parameter (or other parameters to the view) like this

MyView.as_view(slug='hello_world')

Do I need to override any methods to be able to do this?


回答 0

如果您的urlconf看起来像这样:

url(r'^(?P<slug>[a-zA-Z0-9-]+)/$', MyView.as_view(), name = 'my_named_view')

那么该子弹将在您的视图函数(例如“ get_queryset”)中可用,如下所示:

self.kwargs['slug']

If your urlconf looks something like this:

url(r'^(?P<slug>[a-zA-Z0-9-]+)/$', MyView.as_view(), name = 'my_named_view')

then the slug will be available inside your view functions (such as ‘get_queryset’) like this:

self.kwargs['slug']

回答 1

传递给该as_view方法的每个参数都是View类的实例变量。这意味着要添加slug作为参数,您必须在子类中将其创建为实例变量:

# myapp/views.py
from django.views.generic import DetailView

class MyView(DetailView):
    template_name = 'detail.html'
    model = MyModel
    # additional parameters
    slug = None

    def get_object(self, queryset=None):
        return queryset.get(slug=self.slug)

那应该 MyView.as_view(slug='hello_world')起作用。

如果您通过关键字传递变量,请使用Erikkson先生建议的内容:https ://stackoverflow.com/a/11494666/9903

Every parameter that’s passed to the as_view method is an instance variable of the View class. That means to add slug as a parameter you have to create it as an instance variable in your sub-class:

# myapp/views.py
from django.views.generic import DetailView

class MyView(DetailView):
    template_name = 'detail.html'
    model = MyModel
    # additional parameters
    slug = None

    def get_object(self, queryset=None):
        return queryset.get(slug=self.slug)

That should make MyView.as_view(slug='hello_world') work.

If you’re passing the variables through keywords, use what Mr Erikkson suggested: https://stackoverflow.com/a/11494666/9903


回答 2

值得注意的是,您不需要重写get_object()即可基于作为关键字arg传递的段来查找对象-您可以使用SingleObjectMixin https://docs.djangoproject.com/en/1.5/ref/基于类的视图/ mixins-单个对象/#singleobjectmixin

# views.py
class MyView(DetailView):
    model = MyModel
    slug_field = 'slug_field_name'
    slug_url_kwarg = 'model_slug'
    context_object_name = 'my_model'

# urls.py
url(r'^(?P<model_slug>[\w-]+)/$', MyView.as_view(), name = 'my_named_view')

# mymodel_detail.html
{{ my_model.slug_field_name }}

(都slug_fieldslug_url_kwarg默认'slug'

It’s worth noting you don’t need to override get_object() in order to look up an object based on a slug passed as a keyword arg – you can use the attributes of a SingleObjectMixin https://docs.djangoproject.com/en/1.5/ref/class-based-views/mixins-single-object/#singleobjectmixin

# views.py
class MyView(DetailView):
    model = MyModel
    slug_field = 'slug_field_name'
    slug_url_kwarg = 'model_slug'
    context_object_name = 'my_model'

# urls.py
url(r'^(?P<model_slug>[\w-]+)/$', MyView.as_view(), name = 'my_named_view')

# mymodel_detail.html
{{ my_model.slug_field_name }}

(both slug_field and slug_url_kwarg default to 'slug')


回答 3

如果要向模板的上下文中添加对象,则可以覆盖get_context_data并添加到其上下文中。如果您需要request.user,那么请求也是自我的一部分。

def get_context_data(self, **kwargs):
        context = super(MyTemplateView, self).get_context_data(**kwargs)
        if 'slug' in self.kwargs:
            context['object'] = get_object_or_404(MyObject, slug=self.kwargs['slug'])
            context['objects'] = get_objects_by_user(self.request.user)

        return context

If you want to add an object to the context for the template you can override get_context_data and add to its context. The request is also a part of self in case you need the request.user.

def get_context_data(self, **kwargs):
        context = super(MyTemplateView, self).get_context_data(**kwargs)
        if 'slug' in self.kwargs:
            context['object'] = get_object_or_404(MyObject, slug=self.kwargs['slug'])
            context['objects'] = get_objects_by_user(self.request.user)

        return context

回答 4

您可以从urls.py https://docs.djangoproject.com/en/1.7/topics/http/urls/#passing-extra-options-to-view-functions传递参数

这也适用于通用视图。例:

url(r'^$', views.SectionView.as_view(), { 'pk': 'homepage', 'another_param':'?'}, name='main_page'),

在这种情况下,传递给视图的参数不必一定是View类的实例变量。使用此方法,您无需将默认页面名称硬编码到YourView模型中,而只需将其作为参数从urlconf中传递即可。

You can pass parameters from urls.py https://docs.djangoproject.com/en/1.7/topics/http/urls/#passing-extra-options-to-view-functions

This also works for generic views. Example:

url(r'^$', views.SectionView.as_view(), { 'pk': 'homepage', 'another_param':'?'}, name='main_page'),

In this case the parameters passed to the view should not necessarily be instance variables of the View class. Using this method you don’t need to hardcode default page name into YourView model, but you can just pass it as a parameter from urlconf.


回答 5

Yaroslav Nikitenko所述,如果您不想将新的实例变量硬编码到View类,则可以传递额外的选项来查看函数urls.py如下所示:

url(r'^$', YourView.as_view(), {'slug': 'hello_world'}, name='page_name')

我只想从视图中添加如何使用它。您可以实现以下方法之一:

# If slug is optional
def the_function(self, request, slug=None):
    # use slug here

# if slug is an optional param among others
def the_function(self, request, **kwargs):
    slug = kwargs.get("slug", None)
    other_param = kwargs.get("other_param", None)

# If slug is required
def the_function(self, request, slug):
    # use slug here

As stated by Yaroslav Nikitenko, if you don’t want to hardcode a new instance variable to the View class, you can pass extra options to view functions from urls.py like this:

url(r'^$', YourView.as_view(), {'slug': 'hello_world'}, name='page_name')

I just wanted to add how to use it from the view. You can implement one of the following methods:

# If slug is optional
def the_function(self, request, slug=None):
    # use slug here

# if slug is an optional param among others
def the_function(self, request, **kwargs):
    slug = kwargs.get("slug", None)
    other_param = kwargs.get("other_param", None)

# If slug is required
def the_function(self, request, slug):
    # use slug here

回答 6

对于django 3.0,这对我有用:

# myapp/views.py
from django.views.generic import DetailView

class MyView(DetailView):
    template_name = 'detail.html'
    slug = None

    def get_object(self, queryset=None):
        self.slug = self.kwargs.get('slug', None)
        return queryset.get(slug=self.slug)

# myapp/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('slug/<slug:slug>/', views.MyView.as_view(), name='myview_by_tag'),
]

For django 3.0, this is what worked for me:

# myapp/views.py
from django.views.generic import DetailView

class MyView(DetailView):
    template_name = 'detail.html'
    slug = None

    def get_object(self, queryset=None):
        self.slug = self.kwargs.get('slug', None)
        return queryset.get(slug=self.slug)

# myapp/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('slug/<slug:slug>/', views.MyView.as_view(), name='myview_by_tag'),
]