mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
parent
0a2620095a
commit
d4dc3ba246
13 changed files with 143 additions and 194 deletions
26
CHANGES.rst
26
CHANGES.rst
|
|
@ -4,34 +4,28 @@ Changes
|
|||
5.0.0 (WIP)
|
||||
-----------
|
||||
|
||||
- Improve managment commands and separate commands for resetting
|
||||
all access attempts, attempts by IP and attempts by username.
|
||||
Add tests for the management commands for better coverage.
|
||||
- Improve management commands and separate commands for resetting
|
||||
all access attempts, attempts by IP, and attempts by username.
|
||||
[aleksihakli]
|
||||
|
||||
- Add a Django native authentication stack that utilizes
|
||||
``AUTHENTICATION_BACKENDS``, ``MIDDLEWARE``, and signal handlers
|
||||
for tracking login attempts and implementing user lockouts.
|
||||
This results in configuration changes, refer to the documentation.
|
||||
[aleksihakli]
|
||||
- Use backend, middleware, and signal handlers for tracking
|
||||
login attempts and implementing user lockouts.
|
||||
[aleksihakli, jorlugaqui, joshua-s]
|
||||
|
||||
- Add ``AxesDatabaseHandler``, ``AxesCacheHandler``, and ``AxesDummyHandler``
|
||||
handler backends for processing user login and logout events and failures.
|
||||
[aleksihakli, jorlugaqui, joshua-s]
|
||||
|
||||
- Remove automatic decoration of Django login views and forms.
|
||||
Leave decorations available for application users who wish to
|
||||
- Remove automatic decoration and monkey-patching of Django views and forms.
|
||||
Leave decorators available for application users who wish to
|
||||
decorate their own login or other views as before.
|
||||
[aleksihakli]
|
||||
|
||||
- Clean up code, tests, and documentation.
|
||||
Improve test coverage and and raise
|
||||
Codecov monitoring threshold to 90%.
|
||||
- Clean up code, documentation, tests, and coverage.
|
||||
[aleksihakli]
|
||||
|
||||
- Drop support for Python 2.7 and Python 3.4.
|
||||
Require minimum version of Python 3.5+ from now on.
|
||||
Add support for PyPy 3 in the test matrix.
|
||||
- Drop support for Python 2.7, 3.4 and 3.5.
|
||||
Require minimum version of Python 3.6+ from now on.
|
||||
[aleksihakli]
|
||||
|
||||
- Add support for string import for ``AXES_USERNAME_CALLABLE``
|
||||
|
|
|
|||
|
|
@ -8,28 +8,25 @@ from axes.request import AxesHttpRequest
|
|||
|
||||
class AxesBackend(ModelBackend):
|
||||
"""
|
||||
Authentication backend that forbids login attempts for locked out users.
|
||||
Authentication backend class that forbids login attempts for locked out users.
|
||||
|
||||
Use this class as the first item of ``AUTHENTICATION_BACKENDS`` to
|
||||
prevent locked out users from being logged in by the Django authentication flow.
|
||||
|
||||
**Note:** this backend does not log your user in and delegates login to the
|
||||
backends that are configured after it in the ``AUTHENTICATION_BACKENDS`` list.
|
||||
"""
|
||||
|
||||
def authenticate(self, request: AxesHttpRequest, username: str = None, password: str = None, **kwargs):
|
||||
def authenticate(self, request: AxesHttpRequest, username: str = None, password: str = None, **kwargs: dict):
|
||||
"""
|
||||
Check user lock out status and raises PermissionDenied if user is not allowed to log in.
|
||||
Checks user lockout status and raise a PermissionDenied if user is not allowed to log in.
|
||||
|
||||
Inserts errors directly to `return_context` that is supplied as a keyword argument.
|
||||
This method interrupts the login flow and inserts error message directly to the
|
||||
``response_context`` attribute that is supplied as a keyword argument.
|
||||
|
||||
Use this on top of your AUTHENTICATION_BACKENDS list to prevent locked out users
|
||||
from being authenticated in the standard Django authentication flow.
|
||||
|
||||
Note that this method does not log your user in and delegates login to other backends.
|
||||
|
||||
:param request: see django.contrib.auth.backends.ModelBackend.authenticate
|
||||
:param username: see django.contrib.auth.backends.ModelBackend.authenticate
|
||||
:param password: see django.contrib.auth.backends.ModelBackend.authenticate
|
||||
:param kwargs: see django.contrib.auth.backends.ModelBackend.authenticate
|
||||
:keyword response_context: context dict that will be updated with error information
|
||||
:raises AxesBackendRequestParameterRequired: if request parameter is not given correctly
|
||||
:raises AxesBackendPermissionDenied: if user is already locked out
|
||||
:return: None
|
||||
:keyword response_context: kwarg that will be have its ``error`` attribute updated with context.
|
||||
:raises AxesBackendRequestParameterRequired: if request parameter is not passed.
|
||||
:raises AxesBackendPermissionDenied: if user is already locked out.
|
||||
"""
|
||||
|
||||
if request is None:
|
||||
|
|
|
|||
|
|
@ -9,30 +9,21 @@ from axes.request import AxesHttpRequest
|
|||
|
||||
class AxesHandler: # pylint: disable=unused-argument
|
||||
"""
|
||||
Handler API definition for subclassing handlers that can be used with the AxesProxyHandler.
|
||||
|
||||
Public API methods for this class are:
|
||||
|
||||
- is_allowed
|
||||
- user_login_failed
|
||||
- user_logged_in
|
||||
- user_logged_out
|
||||
- post_save_access_attempt
|
||||
- post_delete_access_attempt
|
||||
|
||||
Other API methods are considered internal and do not have fixed signatures.
|
||||
Virtual handler API definition for subclassing handlers that can be used with the ``AxesProxyHandler``.
|
||||
|
||||
If you wish to implement your own handler class just override the methods you wish to specialize
|
||||
and define the class to be used with ``settings.AXES_HANDLER = 'dotted.full.path.to.YourClass'``.
|
||||
and define the class to be used with ``settings.AXES_HANDLER = 'path.to.YourClass'``.
|
||||
|
||||
The default implementation that is actually used by Axes is the ``axes.handlers.database.AxesDatabaseHandler``.
|
||||
"""
|
||||
|
||||
def is_allowed(self, request: AxesHttpRequest, credentials: dict = None) -> bool:
|
||||
"""
|
||||
Check if the user is allowed to access or use given functionality such as a login view or authentication.
|
||||
Checks if the user is allowed to access or use given functionality such as a login view or authentication.
|
||||
|
||||
This method is abstract and other backends can specialize it as needed, but the default implementation
|
||||
checks if the user has attempted to authenticate into the site too many times through the
|
||||
Django authentication backends and returns false if user exceeds the configured Axes thresholds.
|
||||
Django authentication backends and returns ``False``if user exceeds the configured Axes thresholds.
|
||||
|
||||
This checker can implement arbitrary checks such as IP whitelisting or blacklisting,
|
||||
request frequency checking, failed attempt monitoring or similar functions.
|
||||
|
|
@ -54,32 +45,32 @@ class AxesHandler: # pylint: disable=unused-argument
|
|||
|
||||
def user_login_failed(self, sender, credentials: dict, request: AxesHttpRequest = None, **kwargs):
|
||||
"""
|
||||
Handle the Django user_login_failed authentication signal.
|
||||
Handles the Django ``django.contrib.auth.signals.user_login_failed`` authentication signal.
|
||||
"""
|
||||
|
||||
def user_logged_in(self, sender, request: AxesHttpRequest, user, **kwargs):
|
||||
"""
|
||||
Handle the Django user_logged_in authentication signal.
|
||||
Handles the Django ``django.contrib.auth.signals.user_logged_in`` authentication signal.
|
||||
"""
|
||||
|
||||
def user_logged_out(self, sender, request: AxesHttpRequest, user, **kwargs):
|
||||
"""
|
||||
Handle the Django user_logged_out authentication signal.
|
||||
Handles the Django ``django.contrib.auth.signals.user_logged_out`` authentication signal.
|
||||
"""
|
||||
|
||||
def post_save_access_attempt(self, instance, **kwargs):
|
||||
"""
|
||||
Handle the Axes AccessAttempt object post save signal.
|
||||
Handles the ``axes.models.AccessAttempt`` object post save signal.
|
||||
"""
|
||||
|
||||
def post_delete_access_attempt(self, instance, **kwargs):
|
||||
"""
|
||||
Handle the Axes AccessAttempt object post delete signal.
|
||||
Handles the ``axes.models.AccessAttempt`` object post delete signal.
|
||||
"""
|
||||
|
||||
def is_blacklisted(self, request: AxesHttpRequest, credentials: dict = None) -> bool: # pylint: disable=unused-argument
|
||||
"""
|
||||
Check if the request or given credentials are blacklisted from access.
|
||||
Checks if the request or given credentials are blacklisted from access.
|
||||
"""
|
||||
|
||||
if is_client_ip_address_blacklisted(request):
|
||||
|
|
@ -89,7 +80,7 @@ class AxesHandler: # pylint: disable=unused-argument
|
|||
|
||||
def is_whitelisted(self, request: AxesHttpRequest, credentials: dict = None) -> bool: # pylint: disable=unused-argument
|
||||
"""
|
||||
Check if the request or given credentials are whitelisted for access.
|
||||
Checks if the request or given credentials are whitelisted for access.
|
||||
"""
|
||||
|
||||
if is_client_ip_address_whitelisted(request):
|
||||
|
|
@ -102,7 +93,7 @@ class AxesHandler: # pylint: disable=unused-argument
|
|||
|
||||
def is_locked(self, request: AxesHttpRequest, credentials: dict = None) -> bool:
|
||||
"""
|
||||
Check if the request or given credentials are locked.
|
||||
Checks if the request or given credentials are locked.
|
||||
"""
|
||||
|
||||
if settings.AXES_LOCK_OUT_AT_FAILURE:
|
||||
|
|
@ -112,7 +103,10 @@ class AxesHandler: # pylint: disable=unused-argument
|
|||
|
||||
def get_failures(self, request: AxesHttpRequest, credentials: dict = None) -> int:
|
||||
"""
|
||||
Check the number of failures associated to the given request and credentials.
|
||||
Checks the number of failures associated to the given request and credentials.
|
||||
|
||||
This is a virtual method that needs an implementation in the handler subclass
|
||||
if the ``settings.AXES_LOCK_OUT_AT_FAILURE`` flag is set to ``True``.
|
||||
"""
|
||||
|
||||
raise NotImplementedError('The Axes handler class needs a method definition for get_failures')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Callable
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
|
@ -17,38 +19,40 @@ class AxesMiddleware:
|
|||
Middleware that maps lockout signals into readable HTTP 403 Forbidden responses.
|
||||
|
||||
Without this middleware the backend returns HTTP 403 errors with the
|
||||
django.views.defaults.permission_denied view that renders the 403.html
|
||||
``django.views.defaults.permission_denied`` view that renders the ``403.html``
|
||||
template from the root template directory if found.
|
||||
This middleware uses the ``axes.helpers.get_lockout_response`` handler
|
||||
for returning a context aware lockout message to the end user.
|
||||
|
||||
Refer to the Django documentation for further information:
|
||||
|
||||
https://docs.djangoproject.com/en/dev/ref/views/#the-403-http-forbidden-view
|
||||
|
||||
To customize the error rendering, you can for example inherit this middleware
|
||||
and change the process_exception handler to your own liking.
|
||||
To customize the error rendering, you can subclass this middleware
|
||||
and change the ``process_exception`` handler to your own liking.
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
def __init__(self, get_response: Callable):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request: HttpRequest):
|
||||
self.update_request(request)
|
||||
return self.get_response(request)
|
||||
|
||||
def update_request(self, request: HttpRequest):
|
||||
"""
|
||||
Update given Django ``HttpRequest`` with necessary attributes
|
||||
before passing it on the ``get_response`` for further
|
||||
Django middleware and view processing.
|
||||
"""
|
||||
|
||||
request.axes_attempt_time = now()
|
||||
request.axes_ip_address = get_client_ip_address(request)
|
||||
request.axes_user_agent = get_client_user_agent(request)
|
||||
request.axes_path_info = get_client_path_info(request)
|
||||
request.axes_http_accept = get_client_http_accept(request)
|
||||
|
||||
return self.get_response(request)
|
||||
|
||||
def process_exception(self, request: AxesHttpRequest, exception): # pylint: disable=inconsistent-return-statements
|
||||
"""
|
||||
Exception handler that processes exceptions raised by the axes signal handler when request fails with login.
|
||||
Exception handler that processes exceptions raised by the Axes signal handler when request fails with login.
|
||||
|
||||
Refer to axes.signals.log_user_login_failed for the error code.
|
||||
|
||||
:param request: HTTPRequest that will be locked out.
|
||||
:param exception: Exception raised by Django views or signals. Only AxesSignalPermissionDenied will be handled.
|
||||
:return: HTTPResponse that indicates the lockout or None.
|
||||
Only ``axes.exceptions.AxesSignalPermissionDenied`` exception is handled by this middleware.
|
||||
"""
|
||||
|
||||
if isinstance(exception, AxesSignalPermissionDenied):
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class AxesProxyHandlerTestCase(AxesTestCase):
|
|||
self.assertTrue(handler.post_delete_access_attempt.called)
|
||||
|
||||
|
||||
class AxesHandlerTestCase(AxesTestCase):
|
||||
class AxesHandlerBaseTestCase(AxesTestCase):
|
||||
def check_whitelist(self, log):
|
||||
with override_settings(
|
||||
AXES_NEVER_LOCKOUT_WHITELIST=True,
|
||||
|
|
@ -102,7 +102,7 @@ class AxesHandlerTestCase(AxesTestCase):
|
|||
AXES_COOLOFF_TIME=timedelta(seconds=1),
|
||||
AXES_RESET_ON_SUCCESS=True,
|
||||
)
|
||||
class AxesDatabaseHandlerTestCase(AxesHandlerTestCase):
|
||||
class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase):
|
||||
@override_settings(AXES_RESET_ON_SUCCESS=True)
|
||||
def test_handler(self):
|
||||
self.check_handler()
|
||||
|
|
@ -129,7 +129,7 @@ class AxesDatabaseHandlerTestCase(AxesHandlerTestCase):
|
|||
AXES_HANDLER='axes.handlers.cache.AxesCacheHandler',
|
||||
AXES_COOLOFF_TIME=timedelta(seconds=1),
|
||||
)
|
||||
class AxesCacheHandlerTestCase(AxesHandlerTestCase):
|
||||
class AxesCacheHandlerTestCase(AxesHandlerBaseTestCase):
|
||||
@override_settings(AXES_RESET_ON_SUCCESS=True)
|
||||
def test_handler(self):
|
||||
self.check_handler()
|
||||
|
|
@ -150,7 +150,7 @@ class AxesCacheHandlerTestCase(AxesHandlerTestCase):
|
|||
@override_settings(
|
||||
AXES_HANDLER='axes.handlers.dummy.AxesDummyHandler',
|
||||
)
|
||||
class AxesDummyHandlerTestCase(AxesHandlerTestCase):
|
||||
class AxesDummyHandlerTestCase(AxesHandlerBaseTestCase):
|
||||
def test_handler(self):
|
||||
for _ in range(settings.AXES_FAILURE_LIMIT):
|
||||
self.login()
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
.. _reference:
|
||||
|
||||
10. API reference
|
||||
=================
|
||||
|
||||
Axes offers extendable APIs that you can customize to your liking.
|
||||
|
||||
You can specialize the following base classes or alternatively
|
||||
implement your own classes based on the following base implementations.
|
||||
|
||||
|
||||
AxesBackend
|
||||
-----------
|
||||
|
||||
.. automodule:: axes.backends
|
||||
:members:
|
||||
|
||||
|
||||
AxesHandler
|
||||
---------------
|
||||
|
||||
.. automodule:: axes.handlers.base
|
||||
:members:
|
||||
|
||||
|
||||
AxesMiddleware
|
||||
--------------
|
||||
|
||||
.. automodule:: axes.middleware
|
||||
:members:
|
||||
|
|
@ -7,13 +7,9 @@ Axes is easy to install from the PyPI package::
|
|||
|
||||
$ pip install django-axes
|
||||
|
||||
After installing the package, the project settings need to be configured.
|
||||
|
||||
Configuring settings
|
||||
--------------------
|
||||
|
||||
After installing the package, the project ``settings.py`` needs to be configured.
|
||||
|
||||
1. add ``axes`` to your ``INSTALLED_APPS``::
|
||||
**1.** Add ``axes`` to your ``INSTALLED_APPS``::
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
|
|
@ -23,25 +19,21 @@ After installing the package, the project ``settings.py`` needs to be configured
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# ... other applications per your preference.
|
||||
|
||||
# Axes app can be in any position in the INSTALLED_APPS list.
|
||||
'axes',
|
||||
]
|
||||
|
||||
2. add ``axes.backends.AxesBackend`` to the top of ``AUTHENTICATION_BACKENDS``::
|
||||
**2.** Add ``axes.backends.AxesBackend`` to the top of ``AUTHENTICATION_BACKENDS``::
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# AxesBackend should be the first backend in the list.
|
||||
# It stops the authentication flow when a user is locked out.
|
||||
# AxesBackend should be the first backend in the AUTHENTICATION_BACKENDS list.
|
||||
'axes.backends.AxesBackend',
|
||||
|
||||
# ... other authentication backends per your preference.
|
||||
|
||||
# Django ModelBackend is the default authentication backend.
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
]
|
||||
|
||||
3. add ``axes.middleware.AxesMiddleware`` to your list of ``MIDDLEWARE``::
|
||||
**3.** Add ``axes.middleware.AxesMiddleware`` to your list of ``MIDDLEWARE``::
|
||||
|
||||
MIDDLEWARE = [
|
||||
# The following is the list of default middleware in new Django projects.
|
||||
|
|
@ -53,26 +45,21 @@ After installing the package, the project ``settings.py`` needs to be configured
|
|||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
|
||||
# ... other middleware per your preference.
|
||||
|
||||
# AxesMiddleware should be the last middleware in the list.
|
||||
# It pretty formats authentication errors into readable responses.
|
||||
# AxesMiddleware should be the last middleware in the MIDDLEWARE list.
|
||||
'axes.middleware.AxesMiddleware',
|
||||
]
|
||||
|
||||
4. Run ``python manage.py migrate`` to sync the database.
|
||||
**4.** Run ``python manage.py check`` to check the configuration.
|
||||
|
||||
**5.** Run ``python manage.py migrate`` to sync the database.
|
||||
|
||||
Axes is now functional with the default settings and is saving user attempts
|
||||
into your database and locking users out if they exceed the maximum attempts.
|
||||
|
||||
|
||||
Running Django system checks
|
||||
----------------------------
|
||||
|
||||
Use the ``python manage.py check`` command to verify the correct configuration in both
|
||||
development and production environments. It is probably best to use this step as part
|
||||
You should use the ``python manage.py check`` command to verify the correct configuration in both
|
||||
development, staging, and production environments. It is probably best to use this step as part
|
||||
of your regular CI workflows to verify that your project is not misconfigured.
|
||||
|
||||
Axes uses the checks to verify your cache configuration to see that your caches
|
||||
should be functional with the configuration of Axes. Many people have different configurations
|
||||
for their development and production environments.
|
||||
Axes uses checks to verify your Django settings configuration for security and functionality.
|
||||
Many people have different configurations for their development and production environments,
|
||||
and running the application with misconfigured settings can prevent security features from working.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.. _architecture:
|
||||
|
||||
9. Architecture
|
||||
7. Architecture
|
||||
================
|
||||
|
||||
Axes is based on the existing Django authentication backend
|
||||
|
|
@ -116,6 +116,7 @@ Axes implements the lockout flow as follows:
|
|||
|
||||
8. AxesSignalPermissionDenied exception is raised
|
||||
if appropriate and it bubbles up the middleware stack.
|
||||
The exception aborts the Django authentication flow.
|
||||
|
||||
9. AxesMiddleware processes the exception
|
||||
and returns a readable lockout message to the user.
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
.. _upgrading:
|
||||
|
||||
7. Upgrading
|
||||
============
|
||||
|
||||
This page contains upgrade instructions between different Axes
|
||||
versions so that users might more confidently upgrade their installations.
|
||||
|
||||
|
||||
Upgrading from Axes version 4 to 5
|
||||
----------------------------------
|
||||
|
||||
Axes version 5 has a few differences compared to Axes 4.
|
||||
|
||||
- Python 2.7, 3.4 and 3.5 support has been dropped.
|
||||
This is to facilitate new language features and typing support,
|
||||
as maintainers opted for the use of new tools in development.
|
||||
- Login and logout view monkey-patching was removed.
|
||||
Login monitoring is now implemented with signal handlers
|
||||
and locking users out is implemented with a combination
|
||||
of a custom authentication backend, middleware, and signals.
|
||||
This does not change existing logic, but is good to know.
|
||||
- The old decorators function as before and their behaviour is the same.
|
||||
- ``AXES_USERNAME_CALLABLE`` is now always called with two arguments,
|
||||
(``request``, ``credentials``) instead of just ``request``.
|
||||
If you have implemented a custom callable, you need to add
|
||||
the second ``credentials`` argument to the function signature.
|
||||
- ``AXES_USERNAME_CALLABLE`` now supports string paths in addition to callables.
|
||||
- ``axes.backends.AxesBackend.RequestParameterRequired``
|
||||
exception was renamed and retyped from ``Exception`` to ``ValueError``.
|
||||
Exception was moved to ``axes.exception.AxesBackendRequestParameterRequired``.
|
||||
- ``AxesBackend`` now raises a
|
||||
``axes.exceptions.AxesBackendPermissionDenied``
|
||||
exception when user is locked out, which triggers signal handler
|
||||
to run on failed logins, checking user lockout statuses.
|
||||
- Axes lockout signal handler now raises a
|
||||
``axes.exceptions.AxesHandlerPermissionDenied`` exception on lockouts.
|
||||
- ``AxesMiddleware`` was added to process lockout events.
|
||||
The middleware handles the ``axes.exception.AxesHandlerPermissionDenied``
|
||||
exception and converts it to a lockout response.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
.. _development:
|
||||
|
||||
8. Development
|
||||
==============
|
||||
|
||||
You can contribute to this project forking it from GitHub and sending pull requests.
|
||||
|
||||
|
||||
Running tests
|
||||
-------------
|
||||
|
||||
Clone the repository and install the Django version you want. Then run::
|
||||
|
||||
$ tox
|
||||
17
docs/8_reference.rst
Normal file
17
docs/8_reference.rst
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.. _reference:
|
||||
|
||||
8. API reference
|
||||
================
|
||||
|
||||
Axes offers extendable APIs that you can customize to your liking.
|
||||
You can specialize the following base classes or alternatively implement
|
||||
your own classes based on top of the following base implementations.
|
||||
|
||||
.. automodule:: axes.backends
|
||||
:members:
|
||||
|
||||
.. automodule:: axes.middleware
|
||||
:members:
|
||||
|
||||
.. automodule:: axes.handlers.base
|
||||
:members:
|
||||
39
docs/9_development.rst
Normal file
39
docs/9_development.rst
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
.. _development:
|
||||
|
||||
9. Development
|
||||
==============
|
||||
|
||||
You can contribute to this project forking it from GitHub and sending pull requests.
|
||||
|
||||
|
||||
Setting up a development environment
|
||||
------------------------------------
|
||||
|
||||
Fork and clone the repository, initialize a virtual environment and install the requirements::
|
||||
|
||||
$ git clone git@github.com:<fork>/django-axes.git
|
||||
$ cd django-axes
|
||||
$ mkdir ~/.virtualenvs
|
||||
$ python3 -m venv ~/.virtualenvs/django-axes
|
||||
$ source ~/.virtualenvs/bin/activate
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
Unit tests that are in the `axes/tests` folder can be run easily with the ``axes.tests.settings`` configuration::
|
||||
|
||||
$ pytest
|
||||
|
||||
Prospector runs a number of source code style, safety, and complexity checks::
|
||||
|
||||
$ prospector
|
||||
|
||||
Mypy runs static typing checks to verify the source code type annotations and correctness::
|
||||
|
||||
$ mypy .
|
||||
|
||||
Before committing, you can run all the tests against all supported Django versions with tox::
|
||||
|
||||
$ tox
|
||||
|
||||
Tox runs the same tests that are run by Travis, and your code should be good to go if it passes.
|
||||
|
||||
After you have made your changes, open a pull request on GitHub for getting your code upstreamed.
|
||||
|
|
@ -15,10 +15,10 @@ Contents
|
|||
4_configuration
|
||||
5_customization
|
||||
6_integration
|
||||
7_upgrading
|
||||
8_development
|
||||
9_architecture
|
||||
10_reference
|
||||
7_architecture
|
||||
8_reference
|
||||
9_development
|
||||
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue