django-dans-waitlist 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django-dans-waitlist-0.0.1/AUTHORS +1 -0
- django-dans-waitlist-0.0.1/LICENSE +27 -0
- django-dans-waitlist-0.0.1/MANIFEST.in +5 -0
- django-dans-waitlist-0.0.1/PKG-INFO +120 -0
- django-dans-waitlist-0.0.1/README.md +89 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/__init__.py +0 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/admin.py +26 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/api_response.py +47 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/api_response_handler.py +171 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/apps.py +6 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/migrations/0001_initial.py +33 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/migrations/__init__.py +0 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/models.py +42 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/permissions.py +18 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/regex.py +6 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/serializers/__init__.py +0 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/serializers/base.py +60 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/serializers/waitlist.py +19 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/templates/emails/base.html +16 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/templates/emails/waitlist-email.html +8 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/templates/emails/widgets/signature.html +4 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/test/__init__.py +0 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/test/model_tests/__init__.py +0 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/test/model_tests/base.py +25 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/test/model_tests/waitlist/__init__.py +0 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/test/model_tests/waitlist/test_waitlist_entry.py +25 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/test/settings.py +81 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/urls.py +23 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist/views.py +118 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist.egg-info/PKG-INFO +120 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist.egg-info/SOURCES.txt +39 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist.egg-info/dependency_links.txt +1 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist.egg-info/requires.txt +2 -0
- django-dans-waitlist-0.0.1/django_dans_waitlist.egg-info/top_level.txt +1 -0
- django-dans-waitlist-0.0.1/docs/apis.md +23 -0
- django-dans-waitlist-0.0.1/docs/models.md +33 -0
- django-dans-waitlist-0.0.1/docs/release.md +30 -0
- django-dans-waitlist-0.0.1/pyproject.toml +3 -0
- django-dans-waitlist-0.0.1/setup.cfg +38 -0
- django-dans-waitlist-0.0.1/setup.py +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Daniel Nazarian - dnaz@danielnazarian.com
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2024 Daniel Nazarian
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of the Daniel Nazarian nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from this
|
|
16
|
+
software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: django-dans-waitlist
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A Django app to different basic waitlists.
|
|
5
|
+
Home-page: https://www.github.com/dan1229/django_dans_waitlist
|
|
6
|
+
Author: Daniel Nazarian
|
|
7
|
+
Author-email: dnaz@danielnazarian.com
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 3.1
|
|
13
|
+
Classifier: Framework :: Django :: 3.2
|
|
14
|
+
Classifier: Framework :: Django :: 4.0
|
|
15
|
+
Classifier: Framework :: Django :: 4.1
|
|
16
|
+
Classifier: Framework :: Django :: 4.2
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
25
|
+
Requires-Python: <3.12,>=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
License-File: AUTHORS
|
|
29
|
+
|
|
30
|
+
# Django Dans Waitlist
|
|
31
|
+
|
|
32
|
+
[](https://github.com/dan1229/django_dans_waitlist/actions/workflows/lint.yml)
|
|
33
|
+
[](https://github.com/dan1229/django_dans_waitlist/actions/workflows/test-python.yml)
|
|
34
|
+
[](https://codecov.io/gh/dan1229/django_dans_waitlist)
|
|
35
|
+
|
|
36
|
+
## Description
|
|
37
|
+
|
|
38
|
+
A Django app to handle waitlist and basic functionality.
|
|
39
|
+
|
|
40
|
+
Support for `Waitlist` and `WaitlistEntry` models, as well as a `WaitlistManager` to handle common operations.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
1. Install the package via pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install django-dans-waitlist
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. Add "django_dans_waitlist" to your INSTALLED_APPS setting like this:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
INSTALLED_APPS = [
|
|
54
|
+
...
|
|
55
|
+
'django_dans_waitlist',
|
|
56
|
+
]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. Include the URL configs in your project `urls.py` for the REST API endpoints like this:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
path("api/waitlist/", include("django_dans_waitlist.urls")),
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. Run `python manage.py migrate` to update your database schema.
|
|
66
|
+
|
|
67
|
+
5. Use the API endpoints, in code or your Django admin portal.
|
|
68
|
+
|
|
69
|
+
### Requirements
|
|
70
|
+
|
|
71
|
+
- Python 3.10 - 3.11
|
|
72
|
+
- Django 3.1 or higher
|
|
73
|
+
- Django Rest Framework
|
|
74
|
+
- **NOTE:** not only must you have this installed, you must have set `DEFAULT_AUTHENTICATION_CLASSES` and `DEFAULT_PAGINATION_CLASS` in your `settings.py` to work with the APIs properly. An example config would be:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
REST_FRAMEWORK = {
|
|
78
|
+
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
|
79
|
+
"PAGE_SIZE": 20,
|
|
80
|
+
"DEFAULT_AUTHENTICATION_CLASSES": (
|
|
81
|
+
"rest_framework.authentication.TokenAuthentication",
|
|
82
|
+
),
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### Available Settings
|
|
88
|
+
|
|
89
|
+
Currently all available settings are optional:
|
|
90
|
+
|
|
91
|
+
- `TEAM_NAME` - Default team name to use for emails, can be added to message context manually as well still.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Add these to your `settings.py` file to customize the app's behavior like so:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
TEAM_NAME = "My Team"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
|
|
103
|
+
TODO
|
|
104
|
+
|
|
105
|
+
## Docs
|
|
106
|
+
|
|
107
|
+
TODO - which of these are still relevant?
|
|
108
|
+
#### [Model docs](https://github.com/dan1229/django_dans_waitlist/tree/main/docs/models.md).
|
|
109
|
+
|
|
110
|
+
#### [API docs](https://github.com/dan1229/django_dans_waitlist/tree/main/docs/apis.md).
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
-------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
##### [https://danielnazarian.com](https://danielnazarian.com)
|
|
116
|
+
|
|
117
|
+
##### Copyright 2024 © Daniel Nazarian.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Django Dans Waitlist
|
|
2
|
+
|
|
3
|
+
[](https://github.com/dan1229/django_dans_waitlist/actions/workflows/lint.yml)
|
|
4
|
+
[](https://github.com/dan1229/django_dans_waitlist/actions/workflows/test-python.yml)
|
|
5
|
+
[](https://codecov.io/gh/dan1229/django_dans_waitlist)
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
A Django app to handle waitlist and basic functionality.
|
|
10
|
+
|
|
11
|
+
Support for `Waitlist` and `WaitlistEntry` models, as well as a `WaitlistManager` to handle common operations.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
1. Install the package via pip:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install django-dans-waitlist
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Add "django_dans_waitlist" to your INSTALLED_APPS setting like this:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
INSTALLED_APPS = [
|
|
25
|
+
...
|
|
26
|
+
'django_dans_waitlist',
|
|
27
|
+
]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Include the URL configs in your project `urls.py` for the REST API endpoints like this:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
path("api/waitlist/", include("django_dans_waitlist.urls")),
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
4. Run `python manage.py migrate` to update your database schema.
|
|
37
|
+
|
|
38
|
+
5. Use the API endpoints, in code or your Django admin portal.
|
|
39
|
+
|
|
40
|
+
### Requirements
|
|
41
|
+
|
|
42
|
+
- Python 3.10 - 3.11
|
|
43
|
+
- Django 3.1 or higher
|
|
44
|
+
- Django Rest Framework
|
|
45
|
+
- **NOTE:** not only must you have this installed, you must have set `DEFAULT_AUTHENTICATION_CLASSES` and `DEFAULT_PAGINATION_CLASS` in your `settings.py` to work with the APIs properly. An example config would be:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
REST_FRAMEWORK = {
|
|
49
|
+
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
|
50
|
+
"PAGE_SIZE": 20,
|
|
51
|
+
"DEFAULT_AUTHENTICATION_CLASSES": (
|
|
52
|
+
"rest_framework.authentication.TokenAuthentication",
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Available Settings
|
|
59
|
+
|
|
60
|
+
Currently all available settings are optional:
|
|
61
|
+
|
|
62
|
+
- `TEAM_NAME` - Default team name to use for emails, can be added to message context manually as well still.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Add these to your `settings.py` file to customize the app's behavior like so:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
TEAM_NAME = "My Team"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
TODO
|
|
75
|
+
|
|
76
|
+
## Docs
|
|
77
|
+
|
|
78
|
+
TODO - which of these are still relevant?
|
|
79
|
+
#### [Model docs](https://github.com/dan1229/django_dans_waitlist/tree/main/docs/models.md).
|
|
80
|
+
|
|
81
|
+
#### [API docs](https://github.com/dan1229/django_dans_waitlist/tree/main/docs/apis.md).
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
-------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
##### [https://danielnazarian.com](https://danielnazarian.com)
|
|
87
|
+
|
|
88
|
+
##### Copyright 2024 © Daniel Nazarian.
|
|
89
|
+
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from .models import WaitlistEntry
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# WAITLIST ENTRY ========================= #
|
|
7
|
+
#
|
|
8
|
+
class WaitlistEntryAdmin(admin.ModelAdmin):
|
|
9
|
+
list_display = (
|
|
10
|
+
"email",
|
|
11
|
+
"datetime_created",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
search_fields = (
|
|
15
|
+
"id",
|
|
16
|
+
"email",
|
|
17
|
+
"datetime_created",
|
|
18
|
+
)
|
|
19
|
+
readonly_fields = ("id",)
|
|
20
|
+
list_per_page = 100
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
#
|
|
24
|
+
# REGISTER ========================= #
|
|
25
|
+
#
|
|
26
|
+
admin.site.register(WaitlistEntry, WaitlistEntryAdmin)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
============================================================================================ #
|
|
5
|
+
API RESPONSE =============================================================================== #
|
|
6
|
+
============================================================================================ #
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ApiResponse:
|
|
11
|
+
"""
|
|
12
|
+
Response type to standardize response structure and conversions.
|
|
13
|
+
|
|
14
|
+
The point of this class is to standardize the response format for all API responses.
|
|
15
|
+
It does so by having properties that model the response structure you'd like.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
status: Optional[int] = None,
|
|
21
|
+
message: Optional[str] = None,
|
|
22
|
+
results: Optional[object | dict | list] = None,
|
|
23
|
+
error_fields: Optional[dict] = None,
|
|
24
|
+
**kwargs: Any
|
|
25
|
+
) -> None:
|
|
26
|
+
self.status = status
|
|
27
|
+
self.message = message
|
|
28
|
+
self.results = results
|
|
29
|
+
self.error_fields = error_fields
|
|
30
|
+
self.extras = kwargs
|
|
31
|
+
|
|
32
|
+
def dict(self) -> dict:
|
|
33
|
+
"""
|
|
34
|
+
Convert ApiResponse to dict. Primarily to use in actual Response object.
|
|
35
|
+
|
|
36
|
+
:returns: Dict containing ApiResponse object info
|
|
37
|
+
:rtype: dict
|
|
38
|
+
"""
|
|
39
|
+
res = {
|
|
40
|
+
"status": self.status,
|
|
41
|
+
"message": self.message,
|
|
42
|
+
"results": self.results,
|
|
43
|
+
"error_fields": self.error_fields,
|
|
44
|
+
}
|
|
45
|
+
for key, value in self.extras.items():
|
|
46
|
+
res[key] = value
|
|
47
|
+
return res
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from django.core.exceptions import ValidationError
|
|
4
|
+
from django.db import IntegrityError
|
|
5
|
+
from rest_framework.response import Response
|
|
6
|
+
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
|
|
7
|
+
from .api_response import ApiResponse
|
|
8
|
+
|
|
9
|
+
LOGGER_DJANGO = logging.getLogger("django")
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
============================================================================================ #
|
|
13
|
+
API RESPONSE HANDLER ======================================================================= #
|
|
14
|
+
============================================================================================ #
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ApiResponseHandler:
|
|
19
|
+
"""
|
|
20
|
+
Generic response handler to help manage API responses
|
|
21
|
+
|
|
22
|
+
This is used out of the box to provide standardized response formats,
|
|
23
|
+
as well as a centralized place to edit/mangle said responses.
|
|
24
|
+
|
|
25
|
+
Mainly just 'wrap' Responses/data you are already returning, this class
|
|
26
|
+
can help enforce structure or format such that you can have responses
|
|
27
|
+
look however you choose.
|
|
28
|
+
|
|
29
|
+
It works very closely with the ApiResponse class.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
message_error: str = "Error. Please try again later.",
|
|
35
|
+
message_success: str = "Successfully completed request.",
|
|
36
|
+
print_log: bool = True,
|
|
37
|
+
):
|
|
38
|
+
self.message_error = message_error
|
|
39
|
+
self.message_success = message_success
|
|
40
|
+
self.print_log = print_log
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _format_response(
|
|
44
|
+
response: Optional[Response] = None,
|
|
45
|
+
results: Optional[object | dict | list] = None,
|
|
46
|
+
message: Optional[str] = None,
|
|
47
|
+
status: Optional[int] = None,
|
|
48
|
+
error_fields: Optional[dict] = None,
|
|
49
|
+
) -> Response:
|
|
50
|
+
"""Internal function to format responses.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
response (Response): Existing DRF response object to use for response.
|
|
54
|
+
results (list): List of results to include in response.
|
|
55
|
+
message (str): Message to include in response.
|
|
56
|
+
status (int): Status to use in response.
|
|
57
|
+
error_fields (dict, optional): Dictionary of field errors to include - typically provided by Django exceptions. Defaults to None.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Response: DRF response object with desired format - can be used directly in views
|
|
61
|
+
"""
|
|
62
|
+
api_response = ApiResponse(
|
|
63
|
+
message=message, status=status, results=results, error_fields=error_fields
|
|
64
|
+
)
|
|
65
|
+
if response: # response passed -> simply edit
|
|
66
|
+
api_response.extras = response.data
|
|
67
|
+
return Response(api_response.dict(), status=status)
|
|
68
|
+
|
|
69
|
+
def _handle_logging(self, msg: str, print_log: Optional[bool] = None) -> None:
|
|
70
|
+
"""Internal function to handle logging for responses handled by this class.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
print_log (boolean): Whether or not to actually print the message.
|
|
74
|
+
msg (str): Message to print.
|
|
75
|
+
"""
|
|
76
|
+
if print_log is None: # print_log not passed, go by default
|
|
77
|
+
if self.print_log:
|
|
78
|
+
LOGGER_DJANGO.error(msg)
|
|
79
|
+
else: # print_log passed, go by that
|
|
80
|
+
if print_log:
|
|
81
|
+
LOGGER_DJANGO.error(msg)
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# RESPONSES
|
|
85
|
+
#
|
|
86
|
+
def response_success(
|
|
87
|
+
self,
|
|
88
|
+
message: Optional[str] = None,
|
|
89
|
+
results: Optional[object | dict | list] = None,
|
|
90
|
+
response: Optional[Response] = None,
|
|
91
|
+
status: Optional[int] = HTTP_200_OK,
|
|
92
|
+
) -> Response:
|
|
93
|
+
"""
|
|
94
|
+
:param str message: message to include in response
|
|
95
|
+
:param object results: results object/list to include in response
|
|
96
|
+
:param Response response: response object to simply edit
|
|
97
|
+
:param int status: HTTP status to use
|
|
98
|
+
|
|
99
|
+
:returns: response of the desired format
|
|
100
|
+
:rtype: Response
|
|
101
|
+
"""
|
|
102
|
+
# no message = use default
|
|
103
|
+
if not message or message == "":
|
|
104
|
+
message = self.message_success
|
|
105
|
+
message = str(message)
|
|
106
|
+
|
|
107
|
+
return self._format_response(
|
|
108
|
+
response=response, results=results, message=message, status=status
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def response_error(
|
|
112
|
+
self,
|
|
113
|
+
error: Optional[str | Exception] = None,
|
|
114
|
+
error_fields: Optional[dict] = None,
|
|
115
|
+
message: Optional[str] = None,
|
|
116
|
+
results: Optional[object | dict | list] = None,
|
|
117
|
+
response: Optional[Response] = None,
|
|
118
|
+
status: Optional[int] = HTTP_400_BAD_REQUEST,
|
|
119
|
+
print_log: Optional[bool] = True,
|
|
120
|
+
) -> Response:
|
|
121
|
+
"""
|
|
122
|
+
:param str|Exception error: error message to log
|
|
123
|
+
:param dict error_fields: list of fields to include in error response
|
|
124
|
+
:param str message: message to include in response
|
|
125
|
+
:param object results: results object/list to include in response
|
|
126
|
+
:param Response response: response object to simply edit
|
|
127
|
+
:param int status: HTTP status to use
|
|
128
|
+
:param bool print_log: override whether to print this error
|
|
129
|
+
|
|
130
|
+
:returns: response of the desired format
|
|
131
|
+
:rtype: Response
|
|
132
|
+
"""
|
|
133
|
+
# django validation error
|
|
134
|
+
if (
|
|
135
|
+
isinstance(error, ValidationError)
|
|
136
|
+
and hasattr(error, "error_dict")
|
|
137
|
+
and error.error_dict.get("__all__")
|
|
138
|
+
):
|
|
139
|
+
# try to parse ValidationError
|
|
140
|
+
messageDict = error.message_dict.get("__all__")
|
|
141
|
+
if isinstance(messageDict, list) and len(messageDict) > 0:
|
|
142
|
+
messageRes = str(messageDict[0])
|
|
143
|
+
else:
|
|
144
|
+
messageRes = self.message_error
|
|
145
|
+
# integrity error
|
|
146
|
+
elif isinstance(error, IntegrityError):
|
|
147
|
+
messageRes = str(error)
|
|
148
|
+
# no message passed and error isn't one we can parse
|
|
149
|
+
elif not message or message == "": # use default message
|
|
150
|
+
messageRes = self.message_error
|
|
151
|
+
else: # message passed, ensure it's a string
|
|
152
|
+
messageRes = str(message)
|
|
153
|
+
|
|
154
|
+
# error NOT passed, pass as this is probably not intended to be logged
|
|
155
|
+
if not error or error == "":
|
|
156
|
+
pass
|
|
157
|
+
else: # error passed, log it somehow
|
|
158
|
+
if not message or message == "": # message not passed, log error itself
|
|
159
|
+
self._handle_logging(str(error), print_log)
|
|
160
|
+
elif message != error: # message and error different, log both
|
|
161
|
+
self._handle_logging(f"{message} - {error}", print_log)
|
|
162
|
+
else: # error and message both exist and are the same
|
|
163
|
+
self._handle_logging(str(error), print_log)
|
|
164
|
+
|
|
165
|
+
return self._format_response(
|
|
166
|
+
response=response,
|
|
167
|
+
results=results,
|
|
168
|
+
message=messageRes,
|
|
169
|
+
status=status,
|
|
170
|
+
error_fields=error_fields,
|
|
171
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from django.db import migrations, models
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Migration(migrations.Migration):
|
|
6
|
+
initial = True
|
|
7
|
+
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
operations = [
|
|
11
|
+
migrations.CreateModel(
|
|
12
|
+
name="WaitlistEntry",
|
|
13
|
+
fields=[
|
|
14
|
+
(
|
|
15
|
+
"id",
|
|
16
|
+
models.UUIDField(
|
|
17
|
+
default=uuid.uuid4,
|
|
18
|
+
editable=False,
|
|
19
|
+
primary_key=True,
|
|
20
|
+
serialize=False,
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
("datetime_created", models.DateTimeField(auto_now_add=True)),
|
|
24
|
+
("datetime_modified", models.DateTimeField(auto_now=True)),
|
|
25
|
+
("email", models.EmailField(max_length=254, unique=True)),
|
|
26
|
+
],
|
|
27
|
+
options={
|
|
28
|
+
"verbose_name": "Waitlist Entry",
|
|
29
|
+
"verbose_name_plural": "Waitlist Entries",
|
|
30
|
+
"ordering": ["email"],
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
# ==================================================================================== #
|
|
7
|
+
# ABSTRACT BASE MODEL ================================================================ #
|
|
8
|
+
# ==================================================================================== #
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AbstractBaseModel(models.Model):
|
|
13
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
14
|
+
|
|
15
|
+
datetime_created = models.DateTimeField(auto_now_add=True, editable=False)
|
|
16
|
+
datetime_modified = models.DateTimeField(auto_now=True)
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
abstract = True
|
|
20
|
+
ordering = ["datetime_created"]
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return "Abstract Base Model"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
# ==================================================================================== #
|
|
28
|
+
# WAITLIST ENTRY ===================================================================== #
|
|
29
|
+
# ==================================================================================== #
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WaitlistEntry(AbstractBaseModel):
|
|
34
|
+
email = models.EmailField(unique=True)
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
return f"Waitlist Entry: {self.email}"
|
|
38
|
+
|
|
39
|
+
class Meta:
|
|
40
|
+
ordering = ["email"]
|
|
41
|
+
verbose_name = "Waitlist Entry"
|
|
42
|
+
verbose_name_plural = "Waitlist Entries"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from rest_framework import permissions
|
|
3
|
+
from rest_framework.request import Request
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
============================================================================================ #
|
|
7
|
+
BASE PERMISSIONS =========================================================================== #
|
|
8
|
+
============================================================================================ #
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AdminOnly(permissions.BasePermission):
|
|
13
|
+
"""Allows access only to Admins."""
|
|
14
|
+
|
|
15
|
+
def has_permission(self, request: Request, view: Any) -> bool:
|
|
16
|
+
if request.user.is_authenticated and request.user.is_staff:
|
|
17
|
+
return True
|
|
18
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Any, List
|
|
2
|
+
from rest_framework import serializers
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# ===================================================================================
|
|
6
|
+
# BASE SERIALIZER ===================================================================
|
|
7
|
+
# ===================================================================================
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseSerializer(serializers.ModelSerializer):
|
|
12
|
+
"""
|
|
13
|
+
BaseSerializer to inherit from
|
|
14
|
+
|
|
15
|
+
:param bool masked: If True, will mask the serializer.
|
|
16
|
+
Based on `masked_fields` property. Fields in `masked_fields` will only
|
|
17
|
+
show up if `masked` is False
|
|
18
|
+
:param bool ref_serializer: If True, will return a reference serializer.
|
|
19
|
+
Based on `ref_fields` property, fields in `ref_fields` will only show
|
|
20
|
+
up if `ref_serializer` is False. If `ref_fields` is not provided,
|
|
21
|
+
all fields will be returned.
|
|
22
|
+
:param str[] fields: Additional kwargs 'field' that controls which fields to include
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
ref_fields: List[str] = []
|
|
26
|
+
masked_fields: List[str] = []
|
|
27
|
+
masked = True
|
|
28
|
+
ref_serializer = False
|
|
29
|
+
|
|
30
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
31
|
+
# save kwargs for later
|
|
32
|
+
self.kwargs = kwargs
|
|
33
|
+
|
|
34
|
+
# handle 'fields' keyword argument
|
|
35
|
+
# Don't pass the 'fields' arg up to the superclass
|
|
36
|
+
fields = kwargs.pop("fields", None)
|
|
37
|
+
|
|
38
|
+
# if masked serializer, remove masked fields
|
|
39
|
+
self.masked = kwargs.pop("masked", self.masked)
|
|
40
|
+
masked_fields = getattr(self.Meta, "masked_fields", [])
|
|
41
|
+
if self.masked:
|
|
42
|
+
for field in masked_fields:
|
|
43
|
+
self.fields.pop(field)
|
|
44
|
+
|
|
45
|
+
# if ref serializer, remove ref fields
|
|
46
|
+
self.ref_serializer = kwargs.pop("ref_serializer", self.ref_serializer)
|
|
47
|
+
ref_fields = getattr(self.Meta, "ref_fields", [])
|
|
48
|
+
if self.ref_serializer:
|
|
49
|
+
for field in ref_fields:
|
|
50
|
+
self.fields.pop(field)
|
|
51
|
+
|
|
52
|
+
# Instantiate the superclass normally
|
|
53
|
+
super(BaseSerializer, self).__init__(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
# Drop any fields that are not specified in the `fields` argument.
|
|
56
|
+
if fields is not None:
|
|
57
|
+
allowed = set(fields)
|
|
58
|
+
existing = set(self.fields)
|
|
59
|
+
for field_name in existing - allowed:
|
|
60
|
+
self.fields.pop(field_name)
|