diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c3c587cbf1..6276dddcde 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,8 +14,6 @@ jobs:
strategy:
matrix:
python-version:
- - '3.6'
- - '3.7'
- '3.8'
- '3.9'
- '3.10'
@@ -37,18 +35,9 @@ jobs:
- name: Install dependencies
run: python -m pip install --upgrade codecov tox
- - name: Install tox-py
- if: ${{ matrix.python-version == '3.6' }}
- run: python -m pip install --upgrade tox-py
-
- name: Run tox targets for ${{ matrix.python-version }}
- if: ${{ matrix.python-version != '3.6' }}
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
- - name: Run tox targets for ${{ matrix.python-version }}
- if: ${{ matrix.python-version == '3.6' }}
- run: tox --py current
-
- name: Run extra tox targets
if: ${{ matrix.python-version == '3.9' }}
run: |
diff --git a/README.md b/README.md
index cadfd73a53..d32fbc331c 100644
--- a/README.md
+++ b/README.md
@@ -53,8 +53,8 @@ Some reasons you might want to use REST framework:
# Requirements
-* Python 3.6+
-* Django 5.0, 4.2, 4.1, 4.0, 3.2, 3.1, 3.0
+* Python 3.8+
+* Django 5.0, 4.2
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
diff --git a/docs/community/funding.md b/docs/community/funding.md
index 951833682e..10e09bf713 100644
--- a/docs/community/funding.md
+++ b/docs/community/funding.md
@@ -1,398 +1,398 @@
-
-
-
-
-# Funding
-
-If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
-
-**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
-
-Signing up for a paid plan will:
-
-* Directly contribute to faster releases, more features, and higher quality software.
-* Allow more time to be invested in documentation, issue triage, and community support.
-* Safeguard the future development of REST framework.
-
-REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
-
----
-
-## What funding has enabled so far
-
-* The [3.4](https://www.django-rest-framework.org/community/3.4-announcement/) and [3.5](https://www.django-rest-framework.org/community/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
-* The [3.6](https://www.django-rest-framework.org/community/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
-* The [3.7 release](https://www.django-rest-framework.org/community/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
-* The recent [3.8 release](https://www.django-rest-framework.org/community/3.8-announcement/).
-* Tom Christie, the creator of Django REST framework, working on the project full-time.
-* Around 80-90 issues and pull requests closed per month since Tom Christie started working on the project full-time.
-* A community & operations manager position part-time for 4 months, helping mature the business and grow sponsorship.
-* Contracting development time for the work on the JavaScript client library and API documentation tooling.
-
----
-
-## What future funding will enable
-
-* Realtime API support, using WebSockets. This will consist of documentation and support for using REST framework together with Django Channels, plus integrating WebSocket support into the client libraries.
-* Better authentication defaults, possibly bringing JWT & CORS support into the core package.
-* Securing the community & operations manager position long-term.
-* Opening up and securing a part-time position to focus on ticket triage and resolution.
-* Paying for development time on building API client libraries in a range of programming languages. These would be integrated directly into the upcoming API documentation.
-
-Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
-
----
-
-## What our sponsors and users say
-
-> As a developer, Django REST framework feels like an obvious and natural extension to all the great things that make up Django and it's community. Getting started is easy while providing simple abstractions which makes it flexible and customizable. Contributing and supporting Django REST framework helps ensure its future and one way or another it also helps Django, and the Python ecosystem.
->
-> — José Padilla, Django REST framework contributor
-
-
-
-> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
->
-> — Filipe Ximenes, Vinta Software
-
-
-
-> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
-DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
->
-> — Andrew Conti, Django REST framework user
-
----
-
-## Individual plan
-
-This subscription is recommended for individuals with an interest in seeing REST framework continue to improve.
-
-If you are using REST framework as a full-time employee, consider recommending that your company takes out a [corporate plan](#corporate-plans).
-
-
-
-
-
- {{ symbol }}
- {{ rates.personal1 }}
- /month{% if vat %} +VAT{% endif %}
-
-
Individual
-
-
- Support ongoing development
-
-
- Credited on the site
-
-
-
-
-
-
-
-
-*Billing is monthly and you can cancel at any time.*
-
----
-
-## Corporate plans
-
-These subscriptions are recommended for companies and organizations using REST framework either publicly or privately.
-
-In exchange for funding you'll also receive advertising space on our site, allowing you to **promote your company or product to many tens of thousands of developers worldwide**.
-
-Our professional and premium plans also include **priority support**. At any time your engineers can escalate an issue or discussion group thread, and we'll ensure it gets a guaranteed response within the next working day.
-
-
-
-
-
- {{ symbol }}
- {{ rates.corporate1 }}
- /month{% if vat %} +VAT{% endif %}
-
-
Basic
-
-
- Support ongoing development
-
-
- Funding page ad placement
-
-
-
-
-
-
-
-
- {{ symbol }}
- {{ rates.corporate2 }}
- /month{% if vat %} +VAT{% endif %}
-
-
Professional
-
-
- Support ongoing development
-
-
- Sidebar ad placement
-
-
- Priority support for your engineers
-
-
-
-
-
-
-
-
- {{ symbol }}
- {{ rates.corporate3 }}
- /month{% if vat %} +VAT{% endif %}
-
-
Premium
-
-
- Support ongoing development
-
-
- Homepage ad placement
-
-
- Sidebar ad placement
-
-
- Priority support for your engineers
-
-
-
-
-
-
-
-
-
-*Billing is monthly and you can cancel at any time.*
-
-Once you've signed up, we will contact you via email and arrange your ad placements on the site.
-
-For further enquires please contact funding@django-rest-framework.org.
-
----
-
-## Accountability
-
-In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](https://www.encode.io/reports/march-2018/) and regularly include financial reports and cost breakdowns.
-
-
-
-
-
-
-
-
-
-
----
-
-## Frequently asked questions
-
-**Q: Can you issue monthly invoices?**
-A: Yes, we are happy to issue monthly invoices. Please just email us and let us know who to issue the invoice to (name and address) and which email address to send it to each month.
-
-**Q: Does sponsorship include VAT?**
-A: Sponsorship is VAT exempt.
-
-**Q: Do I have to sign up for a certain time period?**
-A: No, we appreciate your support for any time period that is convenient for you. Also, you can cancel your sponsorship anytime.
-
-**Q: Can I pay yearly? Can I pay upfront fox X amount of months at a time?**
-A: We are currently only set up to accept monthly payments. However, if you'd like to support Django REST framework and you can only do yearly/upfront payments, we are happy to work with you and figure out a convenient solution.
-
-**Q: Are you only looking for corporate sponsors?**
-A: No, we value individual sponsors just as much as corporate sponsors and appreciate any kind of support.
-
----
-
-## Our sponsors
-
-
-
-
+
+
+
+
+# Funding
+
+If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
+
+**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
+
+Signing up for a paid plan will:
+
+* Directly contribute to faster releases, more features, and higher quality software.
+* Allow more time to be invested in documentation, issue triage, and community support.
+* Safeguard the future development of REST framework.
+
+REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
+
+---
+
+## What funding has enabled so far
+
+* The [3.4](https://www.django-rest-framework.org/community/3.4-announcement/) and [3.5](https://www.django-rest-framework.org/community/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
+* The [3.6](https://www.django-rest-framework.org/community/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
+* The [3.7 release](https://www.django-rest-framework.org/community/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
+* The recent [3.8 release](https://www.django-rest-framework.org/community/3.8-announcement/).
+* Tom Christie, the creator of Django REST framework, working on the project full-time.
+* Around 80-90 issues and pull requests closed per month since Tom Christie started working on the project full-time.
+* A community & operations manager position part-time for 4 months, helping mature the business and grow sponsorship.
+* Contracting development time for the work on the JavaScript client library and API documentation tooling.
+
+---
+
+## What future funding will enable
+
+* Realtime API support, using WebSockets. This will consist of documentation and support for using REST framework together with Django Channels, plus integrating WebSocket support into the client libraries.
+* Better authentication defaults, possibly bringing JWT & CORS support into the core package.
+* Securing the community & operations manager position long-term.
+* Opening up and securing a part-time position to focus on ticket triage and resolution.
+* Paying for development time on building API client libraries in a range of programming languages. These would be integrated directly into the upcoming API documentation.
+
+Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
+
+---
+
+## What our sponsors and users say
+
+> As a developer, Django REST framework feels like an obvious and natural extension to all the great things that make up Django and it's community. Getting started is easy while providing simple abstractions which makes it flexible and customizable. Contributing and supporting Django REST framework helps ensure its future and one way or another it also helps Django, and the Python ecosystem.
+>
+> — José Padilla, Django REST framework contributor
+
+
+
+> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
+>
+> — Filipe Ximenes, Vinta Software
+
+
+
+> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
+DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
+>
+> — Andrew Conti, Django REST framework user
+
+---
+
+## Individual plan
+
+This subscription is recommended for individuals with an interest in seeing REST framework continue to improve.
+
+If you are using REST framework as a full-time employee, consider recommending that your company takes out a [corporate plan](#corporate-plans).
+
+
+
+
+
+ {{ symbol }}
+ {{ rates.personal1 }}
+ /month{% if vat %} +VAT{% endif %}
+
+
Individual
+
+
+ Support ongoing development
+
+
+ Credited on the site
+
+
+
+
+
+
+
+
+*Billing is monthly and you can cancel at any time.*
+
+---
+
+## Corporate plans
+
+These subscriptions are recommended for companies and organizations using REST framework either publicly or privately.
+
+In exchange for funding you'll also receive advertising space on our site, allowing you to **promote your company or product to many tens of thousands of developers worldwide**.
+
+Our professional and premium plans also include **priority support**. At any time your engineers can escalate an issue or discussion group thread, and we'll ensure it gets a guaranteed response within the next working day.
+
+
+
+
+
+ {{ symbol }}
+ {{ rates.corporate1 }}
+ /month{% if vat %} +VAT{% endif %}
+
+
Basic
+
+
+ Support ongoing development
+
+
+ Funding page ad placement
+
+
+
+
+
+
+
+
+ {{ symbol }}
+ {{ rates.corporate2 }}
+ /month{% if vat %} +VAT{% endif %}
+
+
Professional
+
+
+ Support ongoing development
+
+
+ Sidebar ad placement
+
+
+ Priority support for your engineers
+
+
+
+
+
+
+
+
+ {{ symbol }}
+ {{ rates.corporate3 }}
+ /month{% if vat %} +VAT{% endif %}
+
+
Premium
+
+
+ Support ongoing development
+
+
+ Homepage ad placement
+
+
+ Sidebar ad placement
+
+
+ Priority support for your engineers
+
+
+
+
+
+
+
+
+
+*Billing is monthly and you can cancel at any time.*
+
+Once you've signed up, we will contact you via email and arrange your ad placements on the site.
+
+For further enquires please contact funding@django-rest-framework.org.
+
+---
+
+## Accountability
+
+In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](https://www.encode.io/reports/march-2018/) and regularly include financial reports and cost breakdowns.
+
+
+
+
+
+
+
+
+
+
+---
+
+## Frequently asked questions
+
+**Q: Can you issue monthly invoices?**
+A: Yes, we are happy to issue monthly invoices. Please just email us and let us know who to issue the invoice to (name and address) and which email address to send it to each month.
+
+**Q: Does sponsorship include VAT?**
+A: Sponsorship is VAT exempt.
+
+**Q: Do I have to sign up for a certain time period?**
+A: No, we appreciate your support for any time period that is convenient for you. Also, you can cancel your sponsorship anytime.
+
+**Q: Can I pay yearly? Can I pay upfront fox X amount of months at a time?**
+A: We are currently only set up to accept monthly payments. However, if you'd like to support Django REST framework and you can only do yearly/upfront payments, we are happy to work with you and figure out a convenient solution.
+
+**Q: Are you only looking for corporate sponsors?**
+A: No, we value individual sponsors just as much as corporate sponsors and appreciate any kind of support.
+
+---
+
+## Our sponsors
+
+
+
+
diff --git a/docs/index.md b/docs/index.md
index adc66226ee..864c1d0723 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -86,8 +86,8 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
-* Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12)
-* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0)
+* Django (4.2, 5.0)
+* Python (3.8, 3.9, 3.10, 3.11, 3.12)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
@@ -95,8 +95,8 @@ each Python and Django series.
The following packages are optional:
* [PyYAML][pyyaml], [uritemplate][uriteemplate] (5.1+, 3.0.0+) - Schema generation support.
-* [Markdown][markdown] (3.0.0+) - Markdown support for the browsable API.
-* [Pygments][pygments] (2.4.0+) - Add syntax highlighting to Markdown processing.
+* [Markdown][markdown] (3.3.0+) - Markdown support for the browsable API.
+* [Pygments][pygments] (2.7.0+) - Add syntax highlighting to Markdown processing.
* [django-filter][django-filter] (1.0.1+) - Filtering support.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
diff --git a/docs/topics/html-and-forms.md b/docs/topics/html-and-forms.md
index 17c9e3314c..c7e51c1526 100644
--- a/docs/topics/html-and-forms.md
+++ b/docs/topics/html-and-forms.md
@@ -1,220 +1,220 @@
-# HTML & Forms
-
-REST framework is suitable for returning both API style responses, and regular HTML pages. Additionally, serializers can be used as HTML forms and rendered in templates.
-
-## Rendering HTML
-
-In order to return HTML responses you'll need to use either `TemplateHTMLRenderer`, or `StaticHTMLRenderer`.
-
-The `TemplateHTMLRenderer` class expects the response to contain a dictionary of context data, and renders an HTML page based on a template that must be specified either in the view or on the response.
-
-The `StaticHTMLRender` class expects the response to contain a string of the pre-rendered HTML content.
-
-Because static HTML pages typically have different behavior from API responses you'll probably need to write any HTML views explicitly, rather than relying on the built-in generic views.
-
-Here's an example of a view that returns a list of "Profile" instances, rendered in an HTML template:
-
-**views.py**:
-
- from my_project.example.models import Profile
- from rest_framework.renderers import TemplateHTMLRenderer
- from rest_framework.response import Response
- from rest_framework.views import APIView
-
-
- class ProfileList(APIView):
- renderer_classes = [TemplateHTMLRenderer]
- template_name = 'profile_list.html'
-
- def get(self, request):
- queryset = Profile.objects.all()
- return Response({'profiles': queryset})
-
-**profile_list.html**:
-
-
-
Profiles
-
- {% for profile in profiles %}
-
{{ profile.name }}
- {% endfor %}
-
-
-
-## Rendering Forms
-
-Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
-
-The following view demonstrates an example of using a serializer in a template for viewing and updating a model instance:
-
-**views.py**:
-
- from django.shortcuts import get_object_or_404
- from my_project.example.models import Profile
- from rest_framework.renderers import TemplateHTMLRenderer
- from rest_framework.views import APIView
-
-
- class ProfileDetail(APIView):
- renderer_classes = [TemplateHTMLRenderer]
- template_name = 'profile_detail.html'
-
- def get(self, request, pk):
- profile = get_object_or_404(Profile, pk=pk)
- serializer = ProfileSerializer(profile)
- return Response({'serializer': serializer, 'profile': profile})
-
- def post(self, request, pk):
- profile = get_object_or_404(Profile, pk=pk)
- serializer = ProfileSerializer(profile, data=request.data)
- if not serializer.is_valid():
- return Response({'serializer': serializer, 'profile': profile})
- serializer.save()
- return redirect('profile-list')
-
-**profile_detail.html**:
-
- {% load rest_framework %}
-
-
-
-
Profile - {{ profile.name }}
-
-
-
-
-
-### Using template packs
-
-The `render_form` tag takes an optional `template_pack` argument, that specifies which template directory should be used for rendering the form and form fields.
-
-REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are `horizontal`, `vertical`, and `inline`. The default style is `horizontal`. To use any of these template packs you'll want to also include the Bootstrap 3 CSS.
-
-The following HTML will link to a CDN hosted version of the Bootstrap 3 CSS:
-
-
- …
-
-
-
-Third party packages may include alternate template packs, by bundling a template directory containing the necessary form and field templates.
-
-Let's take a look at how to render each of the three available template packs. For these examples we'll use a single serializer class to present a "Login" form.
-
- class LoginSerializer(serializers.Serializer):
- email = serializers.EmailField(
- max_length=100,
- style={'placeholder': 'Email', 'autofocus': True}
- )
- password = serializers.CharField(
- max_length=100,
- style={'input_type': 'password', 'placeholder': 'Password'}
- )
- remember_me = serializers.BooleanField()
-
----
-
-#### `rest_framework/vertical`
-
-Presents form labels above their corresponding control inputs, using the standard Bootstrap layout.
-
-*This is the default template pack.*
-
- {% load rest_framework %}
-
- ...
-
-
-
-
-
----
-
-#### `rest_framework/horizontal`
-
-Presents labels and controls alongside each other, using a 2/10 column split.
-
-*This is the form style used in the browsable API and admin renderers.*
-
- {% load rest_framework %}
-
- ...
-
-
-
-
-
----
-
-#### `rest_framework/inline`
-
-A compact form style that presents all the controls inline.
-
- {% load rest_framework %}
-
- ...
-
-
-
-
-
-## Field styles
-
-Serializer fields can have their rendering style customized by using the `style` keyword argument. This argument is a dictionary of options that control the template and layout used.
-
-The most common way to customize the field style is to use the `base_template` style keyword argument to select which template in the template pack should be use.
-
-For example, to render a `CharField` as an HTML textarea rather than the default HTML input, you would use something like this:
-
- details = serializers.CharField(
- max_length=1000,
- style={'base_template': 'textarea.html'}
- )
-
-If you instead want a field to be rendered using a custom template that is *not part of an included template pack*, you can instead use the `template` style option, to fully specify a template name:
-
- details = serializers.CharField(
- max_length=1000,
- style={'template': 'my-field-templates/custom-input.html'}
- )
-
-Field templates can also use additional style properties, depending on their type. For example, the `textarea.html` template also accepts a `rows` property that can be used to affect the sizing of the control.
-
- details = serializers.CharField(
- max_length=1000,
- style={'base_template': 'textarea.html', 'rows': 10}
- )
-
-The complete list of `base_template` options and their associated style options is listed below.
-
-base_template | Valid field types | Additional style options
------------------------|-------------------------------------------------------------|-----------------------------------------------
-input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus
-textarea.html | `CharField` | rows, placeholder, hide_label
-select.html | `ChoiceField` or relational field types | hide_label
-radio.html | `ChoiceField` or relational field types | inline, hide_label
-select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
-checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
-checkbox.html | `BooleanField` | hide_label
-fieldset.html | Nested serializer | hide_label
-list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label
+# HTML & Forms
+
+REST framework is suitable for returning both API style responses, and regular HTML pages. Additionally, serializers can be used as HTML forms and rendered in templates.
+
+## Rendering HTML
+
+In order to return HTML responses you'll need to use either `TemplateHTMLRenderer`, or `StaticHTMLRenderer`.
+
+The `TemplateHTMLRenderer` class expects the response to contain a dictionary of context data, and renders an HTML page based on a template that must be specified either in the view or on the response.
+
+The `StaticHTMLRender` class expects the response to contain a string of the pre-rendered HTML content.
+
+Because static HTML pages typically have different behavior from API responses you'll probably need to write any HTML views explicitly, rather than relying on the built-in generic views.
+
+Here's an example of a view that returns a list of "Profile" instances, rendered in an HTML template:
+
+**views.py**:
+
+ from my_project.example.models import Profile
+ from rest_framework.renderers import TemplateHTMLRenderer
+ from rest_framework.response import Response
+ from rest_framework.views import APIView
+
+
+ class ProfileList(APIView):
+ renderer_classes = [TemplateHTMLRenderer]
+ template_name = 'profile_list.html'
+
+ def get(self, request):
+ queryset = Profile.objects.all()
+ return Response({'profiles': queryset})
+
+**profile_list.html**:
+
+
+
Profiles
+
+ {% for profile in profiles %}
+
{{ profile.name }}
+ {% endfor %}
+
+
+
+## Rendering Forms
+
+Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
+
+The following view demonstrates an example of using a serializer in a template for viewing and updating a model instance:
+
+**views.py**:
+
+ from django.shortcuts import get_object_or_404
+ from my_project.example.models import Profile
+ from rest_framework.renderers import TemplateHTMLRenderer
+ from rest_framework.views import APIView
+
+
+ class ProfileDetail(APIView):
+ renderer_classes = [TemplateHTMLRenderer]
+ template_name = 'profile_detail.html'
+
+ def get(self, request, pk):
+ profile = get_object_or_404(Profile, pk=pk)
+ serializer = ProfileSerializer(profile)
+ return Response({'serializer': serializer, 'profile': profile})
+
+ def post(self, request, pk):
+ profile = get_object_or_404(Profile, pk=pk)
+ serializer = ProfileSerializer(profile, data=request.data)
+ if not serializer.is_valid():
+ return Response({'serializer': serializer, 'profile': profile})
+ serializer.save()
+ return redirect('profile-list')
+
+**profile_detail.html**:
+
+ {% load rest_framework %}
+
+
+
+
Profile - {{ profile.name }}
+
+
+
+
+
+### Using template packs
+
+The `render_form` tag takes an optional `template_pack` argument, that specifies which template directory should be used for rendering the form and form fields.
+
+REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are `horizontal`, `vertical`, and `inline`. The default style is `horizontal`. To use any of these template packs you'll want to also include the Bootstrap 3 CSS.
+
+The following HTML will link to a CDN hosted version of the Bootstrap 3 CSS:
+
+
+ …
+
+
+
+Third party packages may include alternate template packs, by bundling a template directory containing the necessary form and field templates.
+
+Let's take a look at how to render each of the three available template packs. For these examples we'll use a single serializer class to present a "Login" form.
+
+ class LoginSerializer(serializers.Serializer):
+ email = serializers.EmailField(
+ max_length=100,
+ style={'placeholder': 'Email', 'autofocus': True}
+ )
+ password = serializers.CharField(
+ max_length=100,
+ style={'input_type': 'password', 'placeholder': 'Password'}
+ )
+ remember_me = serializers.BooleanField()
+
+---
+
+#### `rest_framework/vertical`
+
+Presents form labels above their corresponding control inputs, using the standard Bootstrap layout.
+
+*This is the default template pack.*
+
+ {% load rest_framework %}
+
+ ...
+
+
+
+
+
+---
+
+#### `rest_framework/horizontal`
+
+Presents labels and controls alongside each other, using a 2/10 column split.
+
+*This is the form style used in the browsable API and admin renderers.*
+
+ {% load rest_framework %}
+
+ ...
+
+
+
+
+
+---
+
+#### `rest_framework/inline`
+
+A compact form style that presents all the controls inline.
+
+ {% load rest_framework %}
+
+ ...
+
+
+
+
+
+## Field styles
+
+Serializer fields can have their rendering style customized by using the `style` keyword argument. This argument is a dictionary of options that control the template and layout used.
+
+The most common way to customize the field style is to use the `base_template` style keyword argument to select which template in the template pack should be use.
+
+For example, to render a `CharField` as an HTML textarea rather than the default HTML input, you would use something like this:
+
+ details = serializers.CharField(
+ max_length=1000,
+ style={'base_template': 'textarea.html'}
+ )
+
+If you instead want a field to be rendered using a custom template that is *not part of an included template pack*, you can instead use the `template` style option, to fully specify a template name:
+
+ details = serializers.CharField(
+ max_length=1000,
+ style={'template': 'my-field-templates/custom-input.html'}
+ )
+
+Field templates can also use additional style properties, depending on their type. For example, the `textarea.html` template also accepts a `rows` property that can be used to affect the sizing of the control.
+
+ details = serializers.CharField(
+ max_length=1000,
+ style={'base_template': 'textarea.html', 'rows': 10}
+ )
+
+The complete list of `base_template` options and their associated style options is listed below.
+
+base_template | Valid field types | Additional style options
+-----------------------|-------------------------------------------------------------|-----------------------------------------------
+input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus
+textarea.html | `CharField` | rows, placeholder, hide_label
+select.html | `ChoiceField` or relational field types | hide_label
+radio.html | `ChoiceField` or relational field types | inline, hide_label
+select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
+checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
+checkbox.html | `BooleanField` | hide_label
+fieldset.html | Nested serializer | hide_label
+list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label
diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt
index 25f5121f2e..2cf936ef38 100644
--- a/requirements/requirements-documentation.txt
+++ b/requirements/requirements-documentation.txt
@@ -1,6 +1,5 @@
# MkDocs to build our documentation.
-mkdocs==1.2.4
-jinja2>=2.10,<3.1.0 # contextfilter has been renamed
+mkdocs==1.6.0
# pylinkvalidator to check for broken links in documentation.
pylinkvalidator==0.3
diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt
index e54100f522..bac597c953 100644
--- a/requirements/requirements-optionals.txt
+++ b/requirements/requirements-optionals.txt
@@ -6,5 +6,5 @@ django-guardian>=2.4.0,<2.5
inflection==0.5.1
markdown>=3.3.7
psycopg2-binary>=2.9.5,<2.10
-pygments>=2.12.0,<2.14.0
+pygments~=2.17.0
pyyaml>=5.3.1,<5.4
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index fe2eab04ba..bc16b221b2 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -7,8 +7,6 @@
\_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_|
"""
-import django
-
__title__ = 'Django REST framework'
__version__ = '3.15.1'
__author__ = 'Tom Christie'
@@ -25,11 +23,7 @@
ISO_8601 = 'iso-8601'
-if django.VERSION < (3, 2):
- default_app_config = 'rest_framework.apps.RestFrameworkConfig'
-
-
-class RemovedInDRF315Warning(DeprecationWarning):
+class RemovedInDRF316Warning(DeprecationWarning):
pass
diff --git a/rest_framework/authtoken/__init__.py b/rest_framework/authtoken/__init__.py
index 285fe15c6b..e69de29bb2 100644
--- a/rest_framework/authtoken/__init__.py
+++ b/rest_framework/authtoken/__init__.py
@@ -1,4 +0,0 @@
-import django
-
-if django.VERSION < (3, 2):
- default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index afc06b6cb7..27c5632be5 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -151,30 +151,6 @@ def md_filter_add_syntax_highlight(md):
return False
-if django.VERSION >= (4, 2):
- # Django 4.2+: use the stock parse_header_parameters function
- # Note: Django 4.1 also has an implementation of parse_header_parameters
- # which is slightly different from the one in 4.2, it needs
- # the compatibility shim as well.
- from django.utils.http import parse_header_parameters
-else:
- # Django <= 4.1: create a compatibility shim for parse_header_parameters
- from django.http.multipartparser import parse_header
-
- def parse_header_parameters(line):
- # parse_header works with bytes, but parse_header_parameters
- # works with strings. Call encode to convert the line to bytes.
- main_value_pair, params = parse_header(line.encode())
- return main_value_pair, {
- # parse_header will convert *some* values to string.
- # parse_header_parameters converts *all* values to string.
- # Make sure all values are converted by calling decode on
- # any remaining non-string values.
- k: v if isinstance(v, str) else v.decode()
- for k, v in params.items()
- }
-
-
if django.VERSION >= (5, 1):
# Django 5.1+: use the stock ip_address_validators function
# Note: Before Django 5.1, ip_address_validators returns a tuple containing
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index 435c30c88d..3f4730da84 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -114,10 +114,6 @@ def construct_search(self, field_name, queryset):
if hasattr(field, "path_infos"):
# Update opts to follow the relation.
opts = field.path_infos[-1].to_opts
- # django < 4.1
- elif hasattr(field, 'get_path_info'):
- # Update opts to follow the relation.
- opts = field.get_path_info()[-1].to_opts
# Otherwise, use the field with icontains.
lookup = 'icontains'
return LOOKUP_SEP.join([field_name, lookup])
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index f0fd2b8844..0e8e4bcb8d 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -15,9 +15,9 @@
from django.http.multipartparser import \
MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError
+from django.utils.http import parse_header_parameters
from rest_framework import renderers
-from rest_framework.compat import parse_header_parameters
from rest_framework.exceptions import ParseError
from rest_framework.settings import api_settings
from rest_framework.utils import json
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index db1fdd128b..ea73c6657e 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -19,12 +19,13 @@
from django.template import engines, loader
from django.urls import NoReverseMatch
from django.utils.html import mark_safe
+from django.utils.http import parse_header_parameters
from django.utils.safestring import SafeString
from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
- parse_header_parameters, pygments_css, yaml
+ pygments_css, yaml
)
from rest_framework.exceptions import ParseError
from rest_framework.request import is_form_media_type, override_method
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 93109226d9..f30578fa24 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -16,9 +16,9 @@
from django.http import HttpRequest, QueryDict
from django.http.request import RawPostDataException
from django.utils.datastructures import MultiValueDict
+from django.utils.http import parse_header_parameters
from rest_framework import exceptions
-from rest_framework.compat import parse_header_parameters
from rest_framework.settings import api_settings
diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py
index 38031e646d..f35106fe5a 100644
--- a/rest_framework/schemas/openapi.py
+++ b/rest_framework/schemas/openapi.py
@@ -12,7 +12,7 @@
from django.utils.encoding import force_str
from rest_framework import (
- RemovedInDRF315Warning, exceptions, renderers, serializers
+ RemovedInDRF316Warning, exceptions, renderers, serializers
)
from rest_framework.compat import inflection, uritemplate
from rest_framework.fields import _UnvalidatedField, empty
@@ -725,7 +725,7 @@ def get_tags(self, path, method):
def _get_reference(self, serializer):
warnings.warn(
"Method `_get_reference()` has been renamed to `get_reference()`. "
- "The old name will be removed in DRF v3.15.",
- RemovedInDRF315Warning, stacklevel=2
+ "The old name will be removed in DRF v3.16.",
+ RemovedInDRF316Warning, stacklevel=2
)
return self.get_reference(serializer)
diff --git a/rest_framework/test.py b/rest_framework/test.py
index 04409f9621..e939adcd7e 100644
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -3,7 +3,6 @@
import io
from importlib import import_module
-import django
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import WSGIHandler
@@ -394,19 +393,7 @@ def setUpClass(cls):
cls._override.enable()
- if django.VERSION > (4, 0):
- cls.addClassCleanup(cls._override.disable)
- cls.addClassCleanup(cleanup_url_patterns, cls)
+ cls.addClassCleanup(cls._override.disable)
+ cls.addClassCleanup(cleanup_url_patterns, cls)
super().setUpClass()
-
- if django.VERSION < (4, 0):
- @classmethod
- def tearDownClass(cls):
- super().tearDownClass()
- cls._override.disable()
-
- if hasattr(cls, '_module_urlpatterns'):
- cls._module.urlpatterns = cls._module_urlpatterns
- else:
- del cls._module.urlpatterns
diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py
index b9004d4963..8641732f00 100644
--- a/rest_framework/utils/mediatypes.py
+++ b/rest_framework/utils/mediatypes.py
@@ -3,7 +3,7 @@
See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
"""
-from rest_framework.compat import parse_header_parameters
+from django.utils.http import parse_header_parameters
def media_type_matches(lhs, rhs):
diff --git a/setup.cfg b/setup.cfg
index e7e288816f..4592388360 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,7 +3,7 @@ license_files = LICENSE.md
[tool:pytest]
addopts=--tb=short --strict-markers -ra
-testspath = tests
+testpaths = tests
filterwarnings = ignore:CoreAPI compatibility is deprecated*:rest_framework.RemovedInDRF317Warning
[flake8]
diff --git a/setup.py b/setup.py
index 40898b6c15..d2cfe877e2 100755
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
from setuptools import find_packages, setup
CURRENT_PYTHON = sys.version_info[:2]
-REQUIRED_PYTHON = (3, 6)
+REQUIRED_PYTHON = (3, 8)
# This check and everything above must remain compatible with Python 2.7.
if CURRENT_PYTHON < REQUIRED_PYTHON:
@@ -83,18 +83,13 @@ def get_version(package):
author_email='tom@tomchristie.com', # SEE NOTE BELOW (*)
packages=find_packages(exclude=['tests*']),
include_package_data=True,
- install_requires=["django>=3.0", 'backports.zoneinfo;python_version<"3.9"'],
- python_requires=">=3.6",
+ install_requires=["django>=4.2", 'backports.zoneinfo;python_version<"3.9"'],
+ python_requires=">=3.8",
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
- 'Framework :: Django :: 3.0',
- 'Framework :: Django :: 3.1',
- 'Framework :: Django :: 3.2',
- 'Framework :: Django :: 4.0',
- 'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
'Intended Audience :: Developers',
@@ -102,8 +97,6 @@ def get_version(package):
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py
index 22e837ef40..2f05ce7d19 100644
--- a/tests/authentication/test_authentication.py
+++ b/tests/authentication/test_authentication.py
@@ -1,6 +1,5 @@
import base64
-import django
import pytest
from django.conf import settings
from django.contrib.auth.models import User
@@ -235,21 +234,13 @@ def test_post_form_session_auth_passing_csrf(self):
Ensure POSTing form over session authentication with CSRF token succeeds.
Regression test for #6088
"""
- # Remove this shim when dropping support for Django 3.0.
- if django.VERSION < (3, 1):
- from django.middleware.csrf import _get_new_csrf_token
- else:
- from django.middleware.csrf import (
- _get_new_csrf_string, _mask_cipher_secret
- )
-
- def _get_new_csrf_token():
- return _mask_cipher_secret(_get_new_csrf_string())
-
self.csrf_client.login(username=self.username, password=self.password)
# Set the csrf_token cookie so that CsrfViewMiddleware._get_token() works
- token = _get_new_csrf_token()
+ from django.middleware.csrf import (
+ _get_new_csrf_string, _mask_cipher_secret
+ )
+ token = _mask_cipher_secret(_get_new_csrf_string())
self.csrf_client.cookies[settings.CSRF_COOKIE_NAME] = token
# Post the token matching the cookie value
diff --git a/tests/conftest.py b/tests/conftest.py
index b67475d8a7..01914ae778 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -13,8 +13,6 @@ def pytest_addoption(parser):
def pytest_configure(config):
from django.conf import settings
- # USE_L10N is deprecated, and will be removed in Django 5.0.
- use_l10n = {"USE_L10N": True} if django.VERSION < (4, 0) else {}
settings.configure(
DEBUG_PROPAGATE_EXCEPTIONS=True,
DATABASES={
@@ -64,7 +62,6 @@ def pytest_configure(config):
PASSWORD_HASHERS=(
'django.contrib.auth.hashers.MD5PasswordHasher',
),
- **use_l10n,
)
# guardian is optional
@@ -87,10 +84,7 @@ def pytest_configure(config):
import rest_framework
settings.STATIC_ROOT = os.path.join(os.path.dirname(rest_framework.__file__), 'static-root')
backend = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
- if django.VERSION < (4, 2):
- settings.STATICFILES_STORAGE = backend
- else:
- settings.STORAGES['staticfiles']['BACKEND'] = backend
+ settings.STORAGES['staticfiles']['BACKEND'] = backend
django.setup()
diff --git a/tests/test_description.py b/tests/test_description.py
index ecc6b9776d..7fb93ed4e5 100644
--- a/tests/test_description.py
+++ b/tests/test_description.py
@@ -1,155 +1,155 @@
-import pytest
-from django.test import TestCase
-
-from rest_framework.compat import apply_markdown
-from rest_framework.utils.formatting import dedent
-from rest_framework.views import APIView
-
-# We check that docstrings get nicely un-indented.
-DESCRIPTION = """an example docstring
-====================
-
-* list
-* list
-
-another header
---------------
-
- code block
-
-indented
-
-# hash style header #
-
-```json
-[{
- "alpha": 1,
- "beta": "this is a string"
-}]
-```"""
-
-
-# If markdown is installed we also test it's working
-# (and that our wrapped forces '=' to h2 and '-' to h3)
-MARKDOWN_DOCSTRING = """
an example docstring
-
-
list
-
list
-
-
another header
-
code block
-
-
indented
-
hash style header
-
[{ "alpha":1, "beta":"this is a string" }]
-
"""
-
-
-class TestViewNamesAndDescriptions(TestCase):
- def test_view_name_uses_class_name(self):
- """
- Ensure view names are based on the class name.
- """
- class MockView(APIView):
- pass
- assert MockView().get_view_name() == 'Mock'
-
- def test_view_name_uses_name_attribute(self):
- class MockView(APIView):
- name = 'Foo'
- assert MockView().get_view_name() == 'Foo'
-
- def test_view_name_uses_suffix_attribute(self):
- class MockView(APIView):
- suffix = 'List'
- assert MockView().get_view_name() == 'Mock List'
-
- def test_view_name_preferences_name_over_suffix(self):
- class MockView(APIView):
- name = 'Foo'
- suffix = 'List'
- assert MockView().get_view_name() == 'Foo'
-
- def test_view_description_uses_docstring(self):
- """Ensure view descriptions are based on the docstring."""
- class MockView(APIView):
- """an example docstring
- ====================
-
- * list
- * list
-
- another header
- --------------
-
- code block
-
- indented
-
- # hash style header #
-
- ```json
- [{
- "alpha": 1,
- "beta": "this is a string"
- }]
- ```"""
-
- assert MockView().get_view_description() == DESCRIPTION
-
- def test_view_description_uses_description_attribute(self):
- class MockView(APIView):
- description = 'Foo'
- assert MockView().get_view_description() == 'Foo'
-
- def test_view_description_allows_empty_description(self):
- class MockView(APIView):
- """Description."""
- description = ''
- assert MockView().get_view_description() == ''
-
- def test_view_description_can_be_empty(self):
- """
- Ensure that if a view has no docstring,
- then it's description is the empty string.
- """
- class MockView(APIView):
- pass
- assert MockView().get_view_description() == ''
-
- def test_view_description_can_be_promise(self):
- """
- Ensure a view may have a docstring that is actually a lazily evaluated
- class that can be converted to a string.
-
- See: https://github.com/encode/django-rest-framework/issues/1708
- """
- # use a mock object instead of gettext_lazy to ensure that we can't end
- # up with a test case string in our l10n catalog
-
- class MockLazyStr:
- def __init__(self, string):
- self.s = string
-
- def __str__(self):
- return self.s
-
- class MockView(APIView):
- __doc__ = MockLazyStr("a gettext string")
-
- assert MockView().get_view_description() == 'a gettext string'
-
- @pytest.mark.skipif(not apply_markdown, reason="Markdown is not installed")
- def test_markdown(self):
- """
- Ensure markdown to HTML works as expected.
- """
- assert apply_markdown(DESCRIPTION) == MARKDOWN_DOCSTRING
-
-
-def test_dedent_tabs():
- result = 'first string\n\nsecond string'
- assert dedent(" first string\n\n second string") == result
- assert dedent("first string\n\n second string") == result
- assert dedent("\tfirst string\n\n\tsecond string") == result
- assert dedent("first string\n\n\tsecond string") == result
+import pytest
+from django.test import TestCase
+
+from rest_framework.compat import apply_markdown
+from rest_framework.utils.formatting import dedent
+from rest_framework.views import APIView
+
+# We check that docstrings get nicely un-indented.
+DESCRIPTION = """an example docstring
+====================
+
+* list
+* list
+
+another header
+--------------
+
+ code block
+
+indented
+
+# hash style header #
+
+```json
+[{
+ "alpha": 1,
+ "beta": "this is a string"
+}]
+```"""
+
+
+# If markdown is installed we also test it's working
+# (and that our wrapped forces '=' to h2 and '-' to h3)
+MARKDOWN_DOCSTRING = """
an example docstring
+
+
list
+
list
+
+
another header
+
code block
+
+
indented
+
hash style header
+
[{ "alpha":1, "beta":"this is a string" }]
+
"""
+
+
+class TestViewNamesAndDescriptions(TestCase):
+ def test_view_name_uses_class_name(self):
+ """
+ Ensure view names are based on the class name.
+ """
+ class MockView(APIView):
+ pass
+ assert MockView().get_view_name() == 'Mock'
+
+ def test_view_name_uses_name_attribute(self):
+ class MockView(APIView):
+ name = 'Foo'
+ assert MockView().get_view_name() == 'Foo'
+
+ def test_view_name_uses_suffix_attribute(self):
+ class MockView(APIView):
+ suffix = 'List'
+ assert MockView().get_view_name() == 'Mock List'
+
+ def test_view_name_preferences_name_over_suffix(self):
+ class MockView(APIView):
+ name = 'Foo'
+ suffix = 'List'
+ assert MockView().get_view_name() == 'Foo'
+
+ def test_view_description_uses_docstring(self):
+ """Ensure view descriptions are based on the docstring."""
+ class MockView(APIView):
+ """an example docstring
+ ====================
+
+ * list
+ * list
+
+ another header
+ --------------
+
+ code block
+
+ indented
+
+ # hash style header #
+
+ ```json
+ [{
+ "alpha": 1,
+ "beta": "this is a string"
+ }]
+ ```"""
+
+ assert MockView().get_view_description() == DESCRIPTION
+
+ def test_view_description_uses_description_attribute(self):
+ class MockView(APIView):
+ description = 'Foo'
+ assert MockView().get_view_description() == 'Foo'
+
+ def test_view_description_allows_empty_description(self):
+ class MockView(APIView):
+ """Description."""
+ description = ''
+ assert MockView().get_view_description() == ''
+
+ def test_view_description_can_be_empty(self):
+ """
+ Ensure that if a view has no docstring,
+ then it's description is the empty string.
+ """
+ class MockView(APIView):
+ pass
+ assert MockView().get_view_description() == ''
+
+ def test_view_description_can_be_promise(self):
+ """
+ Ensure a view may have a docstring that is actually a lazily evaluated
+ class that can be converted to a string.
+
+ See: https://github.com/encode/django-rest-framework/issues/1708
+ """
+ # use a mock object instead of gettext_lazy to ensure that we can't end
+ # up with a test case string in our l10n catalog
+
+ class MockLazyStr:
+ def __init__(self, string):
+ self.s = string
+
+ def __str__(self):
+ return self.s
+
+ class MockView(APIView):
+ __doc__ = MockLazyStr("a gettext string")
+
+ assert MockView().get_view_description() == 'a gettext string'
+
+ @pytest.mark.skipif(not apply_markdown, reason="Markdown is not installed")
+ def test_markdown(self):
+ """
+ Ensure markdown to HTML works as expected.
+ """
+ assert apply_markdown(DESCRIPTION) == MARKDOWN_DOCSTRING
+
+
+def test_dedent_tabs():
+ result = 'first string\n\nsecond string'
+ assert dedent(" first string\n\n second string") == result
+ assert dedent("first string\n\n second string") == result
+ assert dedent("\tfirst string\n\n\tsecond string") == result
+ assert dedent("first string\n\n\tsecond string") == result
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 9ac84dd210..4306817634 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1633,7 +1633,7 @@ def test_should_render_date_time_in_default_timezone(self):
assert rendered_date == rendered_date_in_timezone
-@pytest.mark.skipif(pytz is None, reason="As Django 4.0 has deprecated pytz, this test should eventually be able to get removed.")
+@pytest.mark.skipif(pytz is None, reason="Django 5.0 has removed pytz; this test should eventually be able to get removed.")
class TestPytzNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues):
"""
Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST.
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index d69f1652d4..ae1a2b0fa1 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -12,7 +12,6 @@
import sys
import tempfile
-import django
import pytest
from django.core.exceptions import ImproperlyConfigured
from django.core.serializers.json import DjangoJSONEncoder
@@ -453,14 +452,11 @@ class Meta:
model = ArrayFieldModel
fields = ['array_field', 'array_field_with_blank']
- validators = ""
- if django.VERSION < (4, 1):
- validators = ", validators=[]"
expected = dedent("""
TestSerializer():
- array_field = ListField(allow_empty=False, child=CharField(label='Array field'%s))
- array_field_with_blank = ListField(child=CharField(label='Array field with blank'%s), required=False)
- """ % (validators, validators))
+ array_field = ListField(allow_empty=False, child=CharField(label='Array field'))
+ array_field_with_blank = ListField(child=CharField(label='Array field with blank'), required=False)
+ """)
self.assertEqual(repr(TestSerializer()), expected)
@pytest.mark.skipif(hasattr(models, 'JSONField'), reason='has models.JSONField')
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 247737576f..d04ff300ff 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -910,7 +910,7 @@ def test_shell_code_example_rendering(self):
'link': coreapi.Link(url='/data/', action='get', fields=[]),
}
html = template.render(context)
- assert 'testcases list' in html
+ assert 'testcaseslist' in html
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
diff --git a/tests/test_testing.py b/tests/test_testing.py
index 196319a29e..7c2a09fae4 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -2,7 +2,6 @@
from io import BytesIO
from unittest.mock import patch
-import django
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
@@ -334,18 +333,10 @@ def setUpClass(cls):
super().setUpClass()
assert urlpatterns is cls.urlpatterns
- if django.VERSION > (4, 0):
- cls.addClassCleanup(
- check_urlpatterns,
- cls
- )
-
- if django.VERSION < (4, 0):
- @classmethod
- def tearDownClass(cls):
- assert urlpatterns is cls.urlpatterns
- super().tearDownClass()
- assert urlpatterns is not cls.urlpatterns
+ cls.addClassCleanup(
+ check_urlpatterns,
+ cls
+ )
def test_urlpatterns(self):
assert self.client.get('/').status_code == 200
diff --git a/tox.ini b/tox.ini
index ffcbd6729d..16cc3f8f44 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,9 @@
[tox]
envlist =
- {py36,py37,py38,py39}-django30
- {py36,py37,py38,py39}-django31
- {py36,py37,py38,py39,py310}-django32
- {py38,py39,py310}-{django40,django41,django42,djangomain}
- {py311}-{django41,django42,django50,djangomain}
- {py312}-{django42,djanggo50,djangomain}
+ {py38,py39}-{django42}
+ {py310}-{django42,django50,djangomain}
+ {py311}-{django42,django50,djangomain}
+ {py312}-{django42,django50,djangomain}
base
dist
docs
@@ -17,11 +15,6 @@ setenv =
PYTHONDONTWRITEBYTECODE=1
PYTHONWARNINGS=once
deps =
- django30: Django>=3.0,<3.1
- django31: Django>=3.1,<3.2
- django32: Django>=3.2,<4.0
- django40: Django>=4.0,<4.1
- django41: Django>=4.1,<4.2
django42: Django>=4.2,<5.0
django50: Django>=5.0,<5.1
djangomain: https://github.com/django/django/archive/main.tar.gz