coupons 1.1.0__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.
coupons-1.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2026 Nitesh Kumar Singh (nkscoder)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include coupons/migrations *.py
coupons-1.1.0/PKG-INFO ADDED
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: coupons
3
+ Version: 1.1.0
4
+ Summary: Django coupon and discount code system with validation rules — by Nitesh Kumar Singh (nkscoder)
5
+ Author-email: Nitesh Kumar Singh <nkscoder@gmail.com>
6
+ Maintainer-email: Nitesh Kumar Singh <nkscoder@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/nkscoder/coupons
9
+ Project-URL: Documentation, https://github.com/nkscoder/coupons#readme
10
+ Project-URL: Repository, https://github.com/nkscoder/coupons
11
+ Project-URL: Bug Tracker, https://github.com/nkscoder/coupons/issues
12
+ Project-URL: Author GitHub, https://github.com/nkscoder
13
+ Keywords: django,coupons,coupon,discount,promo-code,promotional-code,ecommerce,nkscoder,nitesh-kumar-singh,python,django-coupons,coupon-validation,discount-code
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Framework :: Django
17
+ Classifier: Framework :: Django :: 3.2
18
+ Classifier: Framework :: Django :: 4.0
19
+ Classifier: Framework :: Django :: 4.1
20
+ Classifier: Framework :: Django :: 4.2
21
+ Classifier: Framework :: Django :: 5.0
22
+ Classifier: Framework :: Django :: 5.1
23
+ Classifier: Intended Audience :: Developers
24
+ Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Operating System :: OS Independent
26
+ Classifier: Programming Language :: Python
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Programming Language :: Python :: 3.8
29
+ Classifier: Programming Language :: Python :: 3.9
30
+ Classifier: Programming Language :: Python :: 3.10
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
34
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
35
+ Requires-Python: >=3.8
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: Django>=3.2
39
+ Dynamic: license-file
40
+
41
+ # Coupons — Django Coupon & Discount Code System
42
+
43
+ [![PyPI version](https://badge.fury.io/py/coupons.svg)](https://pypi.org/project/coupons/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/coupons.svg)](https://pypi.org/project/coupons/)
45
+ [![Django](https://img.shields.io/badge/Django-3.2%2B-green.svg)](https://www.djangoproject.com/)
46
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
47
+
48
+ **Author:** [Nitesh Kumar Singh](https://github.com/nkscoder) · **GitHub:** [@nkscoder](https://github.com/nkscoder)
49
+
50
+ A reusable **Django coupons app** for managing promotional codes, discount rules, and coupon validation in Python web applications. Built by **Nitesh Kumar Singh (nkscoder)** for e-commerce, SaaS, and any Django project that needs flexible coupon functionality.
51
+
52
+ > **Keywords:** django coupons · python coupon system · discount code · promo code · coupon validation · nkscoder · nitesh kumar singh
53
+
54
+ ---
55
+
56
+ ## Features
57
+
58
+ - **Percentage or fixed-amount discounts** on any order total
59
+ - **User-specific or global coupons** — restrict codes to selected users or allow all
60
+ - **Usage limits** — max total uses, uses per user, or unlimited
61
+ - **Expiration & active/inactive rules** with datetime-based validity
62
+ - **Django Admin integration** with bulk actions (reset usage, delete expired)
63
+ - **Simple validation API** — one function call to check any coupon code
64
+ - **Configurable coupon code length** via `DSC_COUPON_CODE_LENGTH` setting
65
+
66
+ ---
67
+
68
+ ## Requirements
69
+
70
+ - Python 3.8+
71
+ - Django 3.2+
72
+
73
+ ---
74
+
75
+ ## Installation
76
+
77
+ ### From PyPI (recommended)
78
+
79
+ ```bash
80
+ pip install coupons
81
+ ```
82
+
83
+ ### From GitHub
84
+
85
+ ```bash
86
+ pip install git+https://github.com/nkscoder/coupons.git
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Setup
92
+
93
+ ### Step 1 — Add to `INSTALLED_APPS`
94
+
95
+ ```python
96
+ # settings.py
97
+ INSTALLED_APPS = [
98
+ ...
99
+ "coupons",
100
+ ]
101
+ ```
102
+
103
+ ### Step 2 — Run migrations
104
+
105
+ ```bash
106
+ python manage.py migrate coupons
107
+ ```
108
+
109
+ ### Step 3 — Create coupons in Django Admin
110
+
111
+ Go to `/admin/` and create:
112
+
113
+ 1. **Discount** — set value and type (percentage or fixed)
114
+ 2. **Allowed Users Rule** — select users or enable "All users"
115
+ 3. **Max Uses Rule** — set limits or enable infinite uses
116
+ 4. **Validity Rule** — set expiration date and active status
117
+ 5. **Ruleset** — link the three rules above
118
+ 6. **Coupon** — assign discount and ruleset (code auto-generated)
119
+
120
+ ### Step 4 (optional) — Custom coupon code length
121
+
122
+ ```python
123
+ # settings.py
124
+ DSC_COUPON_CODE_LENGTH = 16 # default is 12
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Usage
130
+
131
+ ### Validate a coupon
132
+
133
+ ```python
134
+ from coupons.validations import validate_coupon
135
+
136
+ coupon_code = "COUPONTEST01"
137
+ user = User.objects.get(username="nitesh")
138
+
139
+ status = validate_coupon(coupon_code=coupon_code, user=user)
140
+ # {'valid': True}
141
+
142
+ if status["valid"]:
143
+ print("Coupon is valid!")
144
+ else:
145
+ print(status["message"]) # e.g. "Coupon does not exist!"
146
+ ```
147
+
148
+ ### Apply a coupon (record usage)
149
+
150
+ ```python
151
+ from coupons.models import Coupon
152
+
153
+ coupon = Coupon.objects.get(code=coupon_code)
154
+ coupon.use_coupon(user=user)
155
+ ```
156
+
157
+ ### Get discount details
158
+
159
+ ```python
160
+ coupon = Coupon.objects.get(code=coupon_code)
161
+ discount = coupon.get_discount()
162
+ # {'value': 50, 'is_percentage': True}
163
+ ```
164
+
165
+ ### Calculate discounted price
166
+
167
+ ```python
168
+ discounted = coupon.get_discounted_value(initial_value=100.0)
169
+ # Returns 50.0 for 50% off, or 80.0 for $20 off
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Validation Rules
175
+
176
+ | Rule | Description |
177
+ |------|-------------|
178
+ | **Allowed Users** | Coupon valid only for selected users, or all users |
179
+ | **Max Uses** | Global usage cap, per-user limit, or infinite |
180
+ | **Validity** | Active flag + expiration datetime |
181
+
182
+ Invalid responses include a human-readable `message` key:
183
+
184
+ ```python
185
+ validate_coupon("DUMMYCODE", user)
186
+ # {'valid': False, 'message': 'Coupon does not exist!'}
187
+ ```
188
+
189
+ ---
190
+
191
+ ## REST API Example
192
+
193
+ See [`examples/`](examples/) for a Django REST Framework integration sample (`ajax_views.py`, `ajax_urls.py`).
194
+
195
+ ---
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ git clone git@github.com:nkscoder/coupons.git
201
+ cd coupons
202
+ pip install -e ".[dev]"
203
+ python manage.py migrate
204
+ python manage.py runserver
205
+ ```
206
+
207
+ ### Build & publish to PyPI
208
+
209
+ ```bash
210
+ pip install build twine
211
+ python -m build
212
+ twine upload dist/*
213
+ ```
214
+
215
+ Or tag a release on GitHub — the included GitHub Action publishes automatically.
216
+
217
+ ---
218
+
219
+ ## Changelog
220
+
221
+ ### 1.1.0
222
+ - Modernized for Django 3.2–5.x and Python 3.8+
223
+ - Added PyPI packaging (`pyproject.toml`)
224
+ - Fixed per-coupon usage validation bug
225
+ - Fixed template mutation in validation responses
226
+ - SEO & documentation update by **Nitesh Kumar Singh (nkscoder)**
227
+
228
+ ### 1.0.0
229
+ - Initial release
230
+
231
+ ---
232
+
233
+ ## Author & Links
234
+
235
+ | | |
236
+ |---|---|
237
+ | **Author** | Nitesh Kumar Singh |
238
+ | **GitHub** | [github.com/nkscoder](https://github.com/nkscoder) |
239
+ | **Repository** | [github.com/nkscoder/coupons](https://github.com/nkscoder/coupons) |
240
+ | **PyPI** | [pypi.org/project/coupons](https://pypi.org/project/coupons) |
241
+
242
+ ---
243
+
244
+ ## License
245
+
246
+ MIT License — Copyright (c) 2020-2026 [Nitesh Kumar Singh (nkscoder)](https://github.com/nkscoder)
@@ -0,0 +1,206 @@
1
+ # Coupons — Django Coupon & Discount Code System
2
+
3
+ [![PyPI version](https://badge.fury.io/py/coupons.svg)](https://pypi.org/project/coupons/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/coupons.svg)](https://pypi.org/project/coupons/)
5
+ [![Django](https://img.shields.io/badge/Django-3.2%2B-green.svg)](https://www.djangoproject.com/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ **Author:** [Nitesh Kumar Singh](https://github.com/nkscoder) · **GitHub:** [@nkscoder](https://github.com/nkscoder)
9
+
10
+ A reusable **Django coupons app** for managing promotional codes, discount rules, and coupon validation in Python web applications. Built by **Nitesh Kumar Singh (nkscoder)** for e-commerce, SaaS, and any Django project that needs flexible coupon functionality.
11
+
12
+ > **Keywords:** django coupons · python coupon system · discount code · promo code · coupon validation · nkscoder · nitesh kumar singh
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - **Percentage or fixed-amount discounts** on any order total
19
+ - **User-specific or global coupons** — restrict codes to selected users or allow all
20
+ - **Usage limits** — max total uses, uses per user, or unlimited
21
+ - **Expiration & active/inactive rules** with datetime-based validity
22
+ - **Django Admin integration** with bulk actions (reset usage, delete expired)
23
+ - **Simple validation API** — one function call to check any coupon code
24
+ - **Configurable coupon code length** via `DSC_COUPON_CODE_LENGTH` setting
25
+
26
+ ---
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.8+
31
+ - Django 3.2+
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ### From PyPI (recommended)
38
+
39
+ ```bash
40
+ pip install coupons
41
+ ```
42
+
43
+ ### From GitHub
44
+
45
+ ```bash
46
+ pip install git+https://github.com/nkscoder/coupons.git
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Setup
52
+
53
+ ### Step 1 — Add to `INSTALLED_APPS`
54
+
55
+ ```python
56
+ # settings.py
57
+ INSTALLED_APPS = [
58
+ ...
59
+ "coupons",
60
+ ]
61
+ ```
62
+
63
+ ### Step 2 — Run migrations
64
+
65
+ ```bash
66
+ python manage.py migrate coupons
67
+ ```
68
+
69
+ ### Step 3 — Create coupons in Django Admin
70
+
71
+ Go to `/admin/` and create:
72
+
73
+ 1. **Discount** — set value and type (percentage or fixed)
74
+ 2. **Allowed Users Rule** — select users or enable "All users"
75
+ 3. **Max Uses Rule** — set limits or enable infinite uses
76
+ 4. **Validity Rule** — set expiration date and active status
77
+ 5. **Ruleset** — link the three rules above
78
+ 6. **Coupon** — assign discount and ruleset (code auto-generated)
79
+
80
+ ### Step 4 (optional) — Custom coupon code length
81
+
82
+ ```python
83
+ # settings.py
84
+ DSC_COUPON_CODE_LENGTH = 16 # default is 12
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Usage
90
+
91
+ ### Validate a coupon
92
+
93
+ ```python
94
+ from coupons.validations import validate_coupon
95
+
96
+ coupon_code = "COUPONTEST01"
97
+ user = User.objects.get(username="nitesh")
98
+
99
+ status = validate_coupon(coupon_code=coupon_code, user=user)
100
+ # {'valid': True}
101
+
102
+ if status["valid"]:
103
+ print("Coupon is valid!")
104
+ else:
105
+ print(status["message"]) # e.g. "Coupon does not exist!"
106
+ ```
107
+
108
+ ### Apply a coupon (record usage)
109
+
110
+ ```python
111
+ from coupons.models import Coupon
112
+
113
+ coupon = Coupon.objects.get(code=coupon_code)
114
+ coupon.use_coupon(user=user)
115
+ ```
116
+
117
+ ### Get discount details
118
+
119
+ ```python
120
+ coupon = Coupon.objects.get(code=coupon_code)
121
+ discount = coupon.get_discount()
122
+ # {'value': 50, 'is_percentage': True}
123
+ ```
124
+
125
+ ### Calculate discounted price
126
+
127
+ ```python
128
+ discounted = coupon.get_discounted_value(initial_value=100.0)
129
+ # Returns 50.0 for 50% off, or 80.0 for $20 off
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Validation Rules
135
+
136
+ | Rule | Description |
137
+ |------|-------------|
138
+ | **Allowed Users** | Coupon valid only for selected users, or all users |
139
+ | **Max Uses** | Global usage cap, per-user limit, or infinite |
140
+ | **Validity** | Active flag + expiration datetime |
141
+
142
+ Invalid responses include a human-readable `message` key:
143
+
144
+ ```python
145
+ validate_coupon("DUMMYCODE", user)
146
+ # {'valid': False, 'message': 'Coupon does not exist!'}
147
+ ```
148
+
149
+ ---
150
+
151
+ ## REST API Example
152
+
153
+ See [`examples/`](examples/) for a Django REST Framework integration sample (`ajax_views.py`, `ajax_urls.py`).
154
+
155
+ ---
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ git clone git@github.com:nkscoder/coupons.git
161
+ cd coupons
162
+ pip install -e ".[dev]"
163
+ python manage.py migrate
164
+ python manage.py runserver
165
+ ```
166
+
167
+ ### Build & publish to PyPI
168
+
169
+ ```bash
170
+ pip install build twine
171
+ python -m build
172
+ twine upload dist/*
173
+ ```
174
+
175
+ Or tag a release on GitHub — the included GitHub Action publishes automatically.
176
+
177
+ ---
178
+
179
+ ## Changelog
180
+
181
+ ### 1.1.0
182
+ - Modernized for Django 3.2–5.x and Python 3.8+
183
+ - Added PyPI packaging (`pyproject.toml`)
184
+ - Fixed per-coupon usage validation bug
185
+ - Fixed template mutation in validation responses
186
+ - SEO & documentation update by **Nitesh Kumar Singh (nkscoder)**
187
+
188
+ ### 1.0.0
189
+ - Initial release
190
+
191
+ ---
192
+
193
+ ## Author & Links
194
+
195
+ | | |
196
+ |---|---|
197
+ | **Author** | Nitesh Kumar Singh |
198
+ | **GitHub** | [github.com/nkscoder](https://github.com/nkscoder) |
199
+ | **Repository** | [github.com/nkscoder/coupons](https://github.com/nkscoder/coupons) |
200
+ | **PyPI** | [pypi.org/project/coupons](https://pypi.org/project/coupons) |
201
+
202
+ ---
203
+
204
+ ## License
205
+
206
+ MIT License — Copyright (c) 2020-2026 [Nitesh Kumar Singh (nkscoder)](https://github.com/nkscoder)
@@ -0,0 +1,3 @@
1
+ """Django coupons app by Nitesh Kumar Singh (nkscoder)."""
2
+
3
+ __version__ = "1.1.0"
@@ -0,0 +1,31 @@
1
+
2
+ from django.contrib.admin import ModelAdmin
3
+ from django.utils import timezone
4
+
5
+
6
+ # Create your actions here
7
+ # ========================
8
+ def reset_coupon_usage(modeladmin, request, queryset):
9
+ for coupon_user in queryset:
10
+ coupon_user.times_used = 0
11
+ coupon_user.save()
12
+
13
+ ModelAdmin.message_user(modeladmin, request, "Coupons reseted!")
14
+
15
+
16
+ def delete_expired_coupons(modeladmin, request, queryset):
17
+ count = 0
18
+ for coupon in queryset:
19
+ expiration_date = coupon.ruleset.validity.expiration_date
20
+ if timezone.now() >= expiration_date:
21
+ coupon.delete()
22
+ count += 1
23
+
24
+ ModelAdmin.message_user(modeladmin, request, "{0} Expired coupons deleted!".format(count))
25
+
26
+
27
+ # Actions short descriptions
28
+ # ==========================
29
+ reset_coupon_usage.short_description = "Reset coupon usage"
30
+ delete_expired_coupons.short_description = "Delete expired coupons"
31
+
@@ -0,0 +1,53 @@
1
+ from django.contrib import admin
2
+
3
+ from .models import (Coupon,
4
+ Discount,
5
+ Ruleset,
6
+ CouponUser,
7
+ AllowedUsersRule,
8
+ MaxUsesRule,
9
+ ValidityRule)
10
+
11
+ from .actions import (reset_coupon_usage, delete_expired_coupons)
12
+
13
+
14
+ # Register your models here.
15
+ # ==========================
16
+ @admin.register(Coupon)
17
+ class CouponAdmin(admin.ModelAdmin):
18
+ list_display = ('code', 'discount', 'ruleset', 'times_used', 'created', )
19
+ actions = [delete_expired_coupons]
20
+
21
+
22
+ @admin.register(Discount)
23
+ class DiscountAdmin(admin.ModelAdmin):
24
+ pass
25
+
26
+
27
+ @admin.register(Ruleset)
28
+ class RulesetAdmin(admin.ModelAdmin):
29
+ list_display = ('__str__', 'allowed_users', 'max_uses', 'validity', )
30
+
31
+
32
+ @admin.register(CouponUser)
33
+ class CouponUserAdmin(admin.ModelAdmin):
34
+ list_display = ('user', 'coupon', 'times_used', )
35
+ actions = [reset_coupon_usage]
36
+
37
+
38
+ @admin.register(AllowedUsersRule)
39
+ class AllowedUsersRuleAdmin(admin.ModelAdmin):
40
+ def get_model_perms(self, request):
41
+ return {}
42
+
43
+
44
+ @admin.register(MaxUsesRule)
45
+ class MaxUsesRuleAdmin(admin.ModelAdmin):
46
+ def get_model_perms(self, request):
47
+ return {}
48
+
49
+
50
+ @admin.register(ValidityRule)
51
+ class ValidityRuleAdmin(admin.ModelAdmin):
52
+ def get_model_perms(self, request):
53
+ return {}
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CouponsConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "coupons"
7
+ verbose_name = "Coupons"
@@ -0,0 +1,19 @@
1
+ import string
2
+ import random
3
+
4
+ from django.conf import settings
5
+
6
+
7
+ def get_coupon_code_length(length=12):
8
+ return settings.DSC_COUPON_CODE_LENGTH if hasattr(settings, 'DSC_COUPON_CODE_LENGTH') else length
9
+
10
+
11
+ def get_user_model():
12
+ return settings.AUTH_USER_MODEL
13
+
14
+
15
+ def get_random_code(length=12):
16
+ length = get_coupon_code_length(length=length)
17
+ return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length))
18
+
19
+
@@ -0,0 +1,112 @@
1
+ # Generated by Django 3.0.8 on 2023-01-13 12:57
2
+
3
+ import coupons.helpers
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+ import django.db.models.deletion
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ initial = True
12
+
13
+ dependencies = [
14
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.CreateModel(
19
+ name='AllowedUsersRule',
20
+ fields=[
21
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22
+ ('all_users', models.BooleanField(default=False, verbose_name='All users?')),
23
+ ('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Users')),
24
+ ],
25
+ options={
26
+ 'verbose_name': 'Allowed User Rule',
27
+ 'verbose_name_plural': 'Allowed User Rules',
28
+ },
29
+ ),
30
+ migrations.CreateModel(
31
+ name='Coupon',
32
+ fields=[
33
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
34
+ ('code', models.CharField(default=coupons.helpers.get_random_code, max_length=12, unique=True, verbose_name='Coupon Code')),
35
+ ('times_used', models.IntegerField(default=0, editable=False, verbose_name='Times used')),
36
+ ('created', models.DateTimeField(editable=False, verbose_name='Created')),
37
+ ],
38
+ ),
39
+ migrations.CreateModel(
40
+ name='Discount',
41
+ fields=[
42
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
43
+ ('value', models.IntegerField(default=0, verbose_name='Value')),
44
+ ('is_percentage', models.BooleanField(default=False, verbose_name='Is percentage?')),
45
+ ],
46
+ options={
47
+ 'verbose_name': 'Discount',
48
+ 'verbose_name_plural': 'Discounts',
49
+ },
50
+ ),
51
+ migrations.CreateModel(
52
+ name='MaxUsesRule',
53
+ fields=[
54
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
55
+ ('max_uses', models.BigIntegerField(default=0, verbose_name='Maximum uses')),
56
+ ('is_infinite', models.BooleanField(default=False, verbose_name='Infinite uses?')),
57
+ ('uses_per_user', models.IntegerField(default=1, verbose_name='Uses per user')),
58
+ ],
59
+ options={
60
+ 'verbose_name': 'Max Uses Rule',
61
+ 'verbose_name_plural': 'Max Uses Rules',
62
+ },
63
+ ),
64
+ migrations.CreateModel(
65
+ name='ValidityRule',
66
+ fields=[
67
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
68
+ ('expiration_date', models.DateTimeField(verbose_name='Expiration date')),
69
+ ('is_active', models.BooleanField(default=False, verbose_name='Is active?')),
70
+ ],
71
+ options={
72
+ 'verbose_name': 'Validity Rule',
73
+ 'verbose_name_plural': 'Validity Rules',
74
+ },
75
+ ),
76
+ migrations.CreateModel(
77
+ name='Ruleset',
78
+ fields=[
79
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
80
+ ('allowed_users', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coupons.AllowedUsersRule', verbose_name='Allowed users rule')),
81
+ ('max_uses', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coupons.MaxUsesRule', verbose_name='Max uses rule')),
82
+ ('validity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coupons.ValidityRule', verbose_name='Validity rule')),
83
+ ],
84
+ options={
85
+ 'verbose_name': 'Ruleset',
86
+ 'verbose_name_plural': 'Rulesets',
87
+ },
88
+ ),
89
+ migrations.CreateModel(
90
+ name='CouponUser',
91
+ fields=[
92
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
93
+ ('times_used', models.IntegerField(default=0, editable=False, verbose_name='Times used')),
94
+ ('coupon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coupons.Coupon', verbose_name='Coupon')),
95
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
96
+ ],
97
+ options={
98
+ 'verbose_name': 'Coupon User',
99
+ 'verbose_name_plural': 'Coupon Users',
100
+ },
101
+ ),
102
+ migrations.AddField(
103
+ model_name='coupon',
104
+ name='discount',
105
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coupons.Discount'),
106
+ ),
107
+ migrations.AddField(
108
+ model_name='coupon',
109
+ name='ruleset',
110
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coupons.Ruleset', verbose_name='Ruleset'),
111
+ ),
112
+ ]
File without changes
@@ -0,0 +1,135 @@
1
+ from django.db import models
2
+ from django.utils import timezone
3
+
4
+ from .helpers import (get_random_code,
5
+ get_coupon_code_length,
6
+ get_user_model)
7
+
8
+
9
+ # Create your models here.
10
+ # ========================
11
+ class Ruleset(models.Model):
12
+ allowed_users = models.ForeignKey('AllowedUsersRule', on_delete=models.CASCADE, verbose_name="Allowed users rule")
13
+ max_uses = models.ForeignKey('MaxUsesRule', on_delete=models.CASCADE, verbose_name="Max uses rule")
14
+ validity = models.ForeignKey('ValidityRule', on_delete=models.CASCADE, verbose_name="Validity rule")
15
+
16
+ def __str__(self):
17
+ return "Ruleset Nº{0}".format(self.id)
18
+
19
+ class Meta:
20
+ verbose_name = "Ruleset"
21
+ verbose_name_plural = "Rulesets"
22
+
23
+
24
+ class AllowedUsersRule(models.Model):
25
+ user_model = get_user_model()
26
+
27
+ users = models.ManyToManyField(user_model, verbose_name="Users", blank=True)
28
+ all_users = models.BooleanField(default=False, verbose_name="All users?")
29
+
30
+ def __str__(self):
31
+ return "AllowedUsersRule Nº{0}".format(self.id)
32
+
33
+ class Meta:
34
+ verbose_name = "Allowed User Rule"
35
+ verbose_name_plural = "Allowed User Rules"
36
+
37
+
38
+ class MaxUsesRule(models.Model):
39
+ max_uses = models.BigIntegerField(default=0, verbose_name="Maximum uses")
40
+ is_infinite = models.BooleanField(default=False, verbose_name="Infinite uses?")
41
+ uses_per_user = models.IntegerField(default=1, verbose_name="Uses per user")
42
+
43
+ def __str__(self):
44
+ return "MaxUsesRule Nº{0}".format(self.id)
45
+
46
+ class Meta:
47
+ verbose_name = "Max Uses Rule"
48
+ verbose_name_plural = "Max Uses Rules"
49
+
50
+
51
+ class ValidityRule(models.Model):
52
+ expiration_date = models.DateTimeField(verbose_name="Expiration date")
53
+ is_active = models.BooleanField(default=False, verbose_name="Is active?")
54
+
55
+ def __str__(self):
56
+ return "ValidityRule Nº{0}".format(self.id)
57
+
58
+ class Meta:
59
+ verbose_name = "Validity Rule"
60
+ verbose_name_plural = "Validity Rules"
61
+
62
+
63
+ class CouponUser(models.Model):
64
+ user_model = get_user_model()
65
+
66
+ user = models.ForeignKey(user_model, on_delete=models.CASCADE, verbose_name="User")
67
+ coupon = models.ForeignKey('Coupon', on_delete=models.CASCADE, verbose_name="Coupon")
68
+ times_used = models.IntegerField(default=0, editable=False, verbose_name="Times used")
69
+
70
+ def __str__(self):
71
+ return str(self.user)
72
+
73
+ class Meta:
74
+ verbose_name = "Coupon User"
75
+ verbose_name_plural = "Coupon Users"
76
+
77
+
78
+ class Discount(models.Model):
79
+ value = models.IntegerField(default=0, verbose_name="Value")
80
+ is_percentage = models.BooleanField(default=False, verbose_name="Is percentage?")
81
+
82
+ def __str__(self):
83
+ if self.is_percentage:
84
+ return "{0}% - Discount".format(self.value)
85
+
86
+ return "${0} - Discount".format(self.value)
87
+
88
+ class Meta:
89
+ verbose_name = "Discount"
90
+ verbose_name_plural = "Discounts"
91
+
92
+
93
+ class Coupon(models.Model):
94
+ code_length = get_coupon_code_length()
95
+
96
+ code = models.CharField(max_length=code_length, default=get_random_code, verbose_name="Coupon Code", unique=True)
97
+ discount = models.ForeignKey('Discount', on_delete=models.CASCADE)
98
+ times_used = models.IntegerField(default=0, editable=False, verbose_name="Times used")
99
+ created = models.DateTimeField(editable=False, verbose_name="Created")
100
+
101
+ ruleset = models.ForeignKey('Ruleset', on_delete=models.CASCADE, verbose_name="Ruleset")
102
+
103
+ def __str__(self):
104
+ return self.code
105
+
106
+ def use_coupon(self, user):
107
+ coupon_user, created = CouponUser.objects.get_or_create(user=user, coupon=self)
108
+ coupon_user.times_used += 1
109
+ coupon_user.save()
110
+
111
+ self.times_used += 1
112
+ self.save()
113
+
114
+ def get_discount(self):
115
+ return {
116
+ "value": self.discount.value,
117
+ "is_percentage": self.discount.is_percentage
118
+ }
119
+
120
+ def get_discounted_value(self, initial_value):
121
+ discount = self.get_discount()
122
+
123
+ if discount['is_percentage']:
124
+ new_price = initial_value - ((initial_value * discount['value']) / 100)
125
+ new_price = new_price if new_price >= 0.0 else 0.0
126
+ else:
127
+ new_price = initial_value - discount['value']
128
+ new_price = new_price if new_price >= 0.0 else 0.0
129
+
130
+ return new_price
131
+
132
+ def save(self, *args, **kwargs):
133
+ if not self.id:
134
+ self.created = timezone.now()
135
+ return super().save(*args, **kwargs)
@@ -0,0 +1,59 @@
1
+ from django.test import TestCase
2
+ from django.contrib.auth import get_user_model
3
+ from django.utils import timezone
4
+ from datetime import timedelta
5
+
6
+ from coupons.models import (
7
+ AllowedUsersRule,
8
+ Coupon,
9
+ Discount,
10
+ MaxUsesRule,
11
+ Ruleset,
12
+ ValidityRule,
13
+ )
14
+ from coupons.validations import validate_coupon
15
+
16
+
17
+ User = get_user_model()
18
+
19
+
20
+ class CouponValidationTests(TestCase):
21
+ def setUp(self):
22
+ self.user = User.objects.create_user(username="testuser", password="pass")
23
+ self.discount = Discount.objects.create(value=10, is_percentage=True)
24
+ self.allowed = AllowedUsersRule.objects.create(all_users=True)
25
+ self.max_uses = MaxUsesRule.objects.create(
26
+ max_uses=100, is_infinite=False, uses_per_user=1
27
+ )
28
+ self.validity = ValidityRule.objects.create(
29
+ expiration_date=timezone.now() + timedelta(days=30),
30
+ is_active=True,
31
+ )
32
+ self.ruleset = Ruleset.objects.create(
33
+ allowed_users=self.allowed,
34
+ max_uses=self.max_uses,
35
+ validity=self.validity,
36
+ )
37
+ self.coupon = Coupon.objects.create(
38
+ code="TESTCODE1234",
39
+ discount=self.discount,
40
+ ruleset=self.ruleset,
41
+ )
42
+
43
+ def test_valid_coupon(self):
44
+ result = validate_coupon("TESTCODE1234", self.user)
45
+ self.assertTrue(result["valid"])
46
+
47
+ def test_invalid_coupon_code(self):
48
+ result = validate_coupon("NOTEXIST", self.user)
49
+ self.assertFalse(result["valid"])
50
+ self.assertEqual(result["message"], "Coupon does not exist!")
51
+
52
+ def test_get_discounted_value_percentage(self):
53
+ self.assertEqual(self.coupon.get_discounted_value(100.0), 90.0)
54
+
55
+ def test_get_discounted_value_fixed(self):
56
+ self.discount.is_percentage = False
57
+ self.discount.value = 20
58
+ self.discount.save()
59
+ self.assertEqual(self.coupon.get_discounted_value(100.0), 80.0)
@@ -0,0 +1,74 @@
1
+ from .models import Coupon, CouponUser
2
+ from django.utils import timezone
3
+
4
+
5
+ INVALID_TEMPLATE = {
6
+ "valid": False,
7
+ "message": ""
8
+ }
9
+
10
+ VALID_TEMPLATE = {
11
+ "valid": True
12
+ }
13
+
14
+
15
+ def assemble_invalid_message(message=""):
16
+ return {"valid": False, "message": message}
17
+
18
+
19
+ def validate_allowed_users_rule(coupon_object, user):
20
+ allowed_users_rule = coupon_object.ruleset.allowed_users
21
+ if user not in allowed_users_rule.users.all():
22
+ return False if not allowed_users_rule.all_users else True
23
+
24
+ return True
25
+
26
+
27
+ def validate_max_uses_rule(coupon_object, user):
28
+ max_uses_rule = coupon_object.ruleset.max_uses
29
+ if coupon_object.times_used >= max_uses_rule.max_uses and not max_uses_rule.is_infinite:
30
+ return False
31
+
32
+ try:
33
+ coupon_user = CouponUser.objects.get(user=user, coupon=coupon_object)
34
+ if coupon_user.times_used >= max_uses_rule.uses_per_user:
35
+ return False
36
+ except CouponUser.DoesNotExist:
37
+ pass
38
+
39
+ return True
40
+
41
+
42
+ def validate_validity_rule(coupon_object):
43
+ validity_rule = coupon_object.ruleset.validity
44
+ if timezone.now() > validity_rule.expiration_date:
45
+ return False
46
+
47
+ return validity_rule.is_active
48
+
49
+
50
+ def validate_coupon(coupon_code, user):
51
+ if not coupon_code:
52
+ return assemble_invalid_message(message="No coupon code provided!")
53
+
54
+ if not user:
55
+ return assemble_invalid_message(message="No user provided!")
56
+
57
+ try:
58
+ coupon_object = Coupon.objects.get(code=coupon_code)
59
+ except Coupon.DoesNotExist:
60
+ return assemble_invalid_message(message="Coupon does not exist!")
61
+
62
+ valid_allowed_users_rule = validate_allowed_users_rule(coupon_object=coupon_object, user=user)
63
+ if not valid_allowed_users_rule:
64
+ return assemble_invalid_message(message="Invalid coupon for this user!")
65
+
66
+ valid_max_uses_rule = validate_max_uses_rule(coupon_object=coupon_object, user=user)
67
+ if not valid_max_uses_rule:
68
+ return assemble_invalid_message(message="Coupon uses exceeded for this user!")
69
+
70
+ valid_validity_rule = validate_validity_rule(coupon_object=coupon_object)
71
+ if not valid_validity_rule:
72
+ return assemble_invalid_message(message="Invalid coupon!")
73
+
74
+ return VALID_TEMPLATE
@@ -0,0 +1 @@
1
+ # Create your views here.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: coupons
3
+ Version: 1.1.0
4
+ Summary: Django coupon and discount code system with validation rules — by Nitesh Kumar Singh (nkscoder)
5
+ Author-email: Nitesh Kumar Singh <nkscoder@gmail.com>
6
+ Maintainer-email: Nitesh Kumar Singh <nkscoder@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/nkscoder/coupons
9
+ Project-URL: Documentation, https://github.com/nkscoder/coupons#readme
10
+ Project-URL: Repository, https://github.com/nkscoder/coupons
11
+ Project-URL: Bug Tracker, https://github.com/nkscoder/coupons/issues
12
+ Project-URL: Author GitHub, https://github.com/nkscoder
13
+ Keywords: django,coupons,coupon,discount,promo-code,promotional-code,ecommerce,nkscoder,nitesh-kumar-singh,python,django-coupons,coupon-validation,discount-code
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Framework :: Django
17
+ Classifier: Framework :: Django :: 3.2
18
+ Classifier: Framework :: Django :: 4.0
19
+ Classifier: Framework :: Django :: 4.1
20
+ Classifier: Framework :: Django :: 4.2
21
+ Classifier: Framework :: Django :: 5.0
22
+ Classifier: Framework :: Django :: 5.1
23
+ Classifier: Intended Audience :: Developers
24
+ Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Operating System :: OS Independent
26
+ Classifier: Programming Language :: Python
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Programming Language :: Python :: 3.8
29
+ Classifier: Programming Language :: Python :: 3.9
30
+ Classifier: Programming Language :: Python :: 3.10
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
34
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
35
+ Requires-Python: >=3.8
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: Django>=3.2
39
+ Dynamic: license-file
40
+
41
+ # Coupons — Django Coupon & Discount Code System
42
+
43
+ [![PyPI version](https://badge.fury.io/py/coupons.svg)](https://pypi.org/project/coupons/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/coupons.svg)](https://pypi.org/project/coupons/)
45
+ [![Django](https://img.shields.io/badge/Django-3.2%2B-green.svg)](https://www.djangoproject.com/)
46
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
47
+
48
+ **Author:** [Nitesh Kumar Singh](https://github.com/nkscoder) · **GitHub:** [@nkscoder](https://github.com/nkscoder)
49
+
50
+ A reusable **Django coupons app** for managing promotional codes, discount rules, and coupon validation in Python web applications. Built by **Nitesh Kumar Singh (nkscoder)** for e-commerce, SaaS, and any Django project that needs flexible coupon functionality.
51
+
52
+ > **Keywords:** django coupons · python coupon system · discount code · promo code · coupon validation · nkscoder · nitesh kumar singh
53
+
54
+ ---
55
+
56
+ ## Features
57
+
58
+ - **Percentage or fixed-amount discounts** on any order total
59
+ - **User-specific or global coupons** — restrict codes to selected users or allow all
60
+ - **Usage limits** — max total uses, uses per user, or unlimited
61
+ - **Expiration & active/inactive rules** with datetime-based validity
62
+ - **Django Admin integration** with bulk actions (reset usage, delete expired)
63
+ - **Simple validation API** — one function call to check any coupon code
64
+ - **Configurable coupon code length** via `DSC_COUPON_CODE_LENGTH` setting
65
+
66
+ ---
67
+
68
+ ## Requirements
69
+
70
+ - Python 3.8+
71
+ - Django 3.2+
72
+
73
+ ---
74
+
75
+ ## Installation
76
+
77
+ ### From PyPI (recommended)
78
+
79
+ ```bash
80
+ pip install coupons
81
+ ```
82
+
83
+ ### From GitHub
84
+
85
+ ```bash
86
+ pip install git+https://github.com/nkscoder/coupons.git
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Setup
92
+
93
+ ### Step 1 — Add to `INSTALLED_APPS`
94
+
95
+ ```python
96
+ # settings.py
97
+ INSTALLED_APPS = [
98
+ ...
99
+ "coupons",
100
+ ]
101
+ ```
102
+
103
+ ### Step 2 — Run migrations
104
+
105
+ ```bash
106
+ python manage.py migrate coupons
107
+ ```
108
+
109
+ ### Step 3 — Create coupons in Django Admin
110
+
111
+ Go to `/admin/` and create:
112
+
113
+ 1. **Discount** — set value and type (percentage or fixed)
114
+ 2. **Allowed Users Rule** — select users or enable "All users"
115
+ 3. **Max Uses Rule** — set limits or enable infinite uses
116
+ 4. **Validity Rule** — set expiration date and active status
117
+ 5. **Ruleset** — link the three rules above
118
+ 6. **Coupon** — assign discount and ruleset (code auto-generated)
119
+
120
+ ### Step 4 (optional) — Custom coupon code length
121
+
122
+ ```python
123
+ # settings.py
124
+ DSC_COUPON_CODE_LENGTH = 16 # default is 12
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Usage
130
+
131
+ ### Validate a coupon
132
+
133
+ ```python
134
+ from coupons.validations import validate_coupon
135
+
136
+ coupon_code = "COUPONTEST01"
137
+ user = User.objects.get(username="nitesh")
138
+
139
+ status = validate_coupon(coupon_code=coupon_code, user=user)
140
+ # {'valid': True}
141
+
142
+ if status["valid"]:
143
+ print("Coupon is valid!")
144
+ else:
145
+ print(status["message"]) # e.g. "Coupon does not exist!"
146
+ ```
147
+
148
+ ### Apply a coupon (record usage)
149
+
150
+ ```python
151
+ from coupons.models import Coupon
152
+
153
+ coupon = Coupon.objects.get(code=coupon_code)
154
+ coupon.use_coupon(user=user)
155
+ ```
156
+
157
+ ### Get discount details
158
+
159
+ ```python
160
+ coupon = Coupon.objects.get(code=coupon_code)
161
+ discount = coupon.get_discount()
162
+ # {'value': 50, 'is_percentage': True}
163
+ ```
164
+
165
+ ### Calculate discounted price
166
+
167
+ ```python
168
+ discounted = coupon.get_discounted_value(initial_value=100.0)
169
+ # Returns 50.0 for 50% off, or 80.0 for $20 off
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Validation Rules
175
+
176
+ | Rule | Description |
177
+ |------|-------------|
178
+ | **Allowed Users** | Coupon valid only for selected users, or all users |
179
+ | **Max Uses** | Global usage cap, per-user limit, or infinite |
180
+ | **Validity** | Active flag + expiration datetime |
181
+
182
+ Invalid responses include a human-readable `message` key:
183
+
184
+ ```python
185
+ validate_coupon("DUMMYCODE", user)
186
+ # {'valid': False, 'message': 'Coupon does not exist!'}
187
+ ```
188
+
189
+ ---
190
+
191
+ ## REST API Example
192
+
193
+ See [`examples/`](examples/) for a Django REST Framework integration sample (`ajax_views.py`, `ajax_urls.py`).
194
+
195
+ ---
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ git clone git@github.com:nkscoder/coupons.git
201
+ cd coupons
202
+ pip install -e ".[dev]"
203
+ python manage.py migrate
204
+ python manage.py runserver
205
+ ```
206
+
207
+ ### Build & publish to PyPI
208
+
209
+ ```bash
210
+ pip install build twine
211
+ python -m build
212
+ twine upload dist/*
213
+ ```
214
+
215
+ Or tag a release on GitHub — the included GitHub Action publishes automatically.
216
+
217
+ ---
218
+
219
+ ## Changelog
220
+
221
+ ### 1.1.0
222
+ - Modernized for Django 3.2–5.x and Python 3.8+
223
+ - Added PyPI packaging (`pyproject.toml`)
224
+ - Fixed per-coupon usage validation bug
225
+ - Fixed template mutation in validation responses
226
+ - SEO & documentation update by **Nitesh Kumar Singh (nkscoder)**
227
+
228
+ ### 1.0.0
229
+ - Initial release
230
+
231
+ ---
232
+
233
+ ## Author & Links
234
+
235
+ | | |
236
+ |---|---|
237
+ | **Author** | Nitesh Kumar Singh |
238
+ | **GitHub** | [github.com/nkscoder](https://github.com/nkscoder) |
239
+ | **Repository** | [github.com/nkscoder/coupons](https://github.com/nkscoder/coupons) |
240
+ | **PyPI** | [pypi.org/project/coupons](https://pypi.org/project/coupons) |
241
+
242
+ ---
243
+
244
+ ## License
245
+
246
+ MIT License — Copyright (c) 2020-2026 [Nitesh Kumar Singh (nkscoder)](https://github.com/nkscoder)
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ coupons/__init__.py
6
+ coupons/actions.py
7
+ coupons/admin.py
8
+ coupons/apps.py
9
+ coupons/helpers.py
10
+ coupons/models.py
11
+ coupons/tests.py
12
+ coupons/validations.py
13
+ coupons/views.py
14
+ coupons.egg-info/PKG-INFO
15
+ coupons.egg-info/SOURCES.txt
16
+ coupons.egg-info/dependency_links.txt
17
+ coupons.egg-info/requires.txt
18
+ coupons.egg-info/top_level.txt
19
+ coupons/migrations/0001_initial.py
20
+ coupons/migrations/__init__.py
@@ -0,0 +1 @@
1
+ Django>=3.2
@@ -0,0 +1 @@
1
+ coupons
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "coupons"
7
+ version = "1.1.0"
8
+ description = "Django coupon and discount code system with validation rules — by Nitesh Kumar Singh (nkscoder)"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [
12
+ { name = "Nitesh Kumar Singh", email = "nkscoder@gmail.com" },
13
+ ]
14
+ maintainers = [
15
+ { name = "Nitesh Kumar Singh", email = "nkscoder@gmail.com" },
16
+ ]
17
+ keywords = [
18
+ "django",
19
+ "coupons",
20
+ "coupon",
21
+ "discount",
22
+ "promo-code",
23
+ "promotional-code",
24
+ "ecommerce",
25
+ "nkscoder",
26
+ "nitesh-kumar-singh",
27
+ "python",
28
+ "django-coupons",
29
+ "coupon-validation",
30
+ "discount-code",
31
+ ]
32
+ classifiers = [
33
+ "Development Status :: 5 - Production/Stable",
34
+ "Environment :: Web Environment",
35
+ "Framework :: Django",
36
+ "Framework :: Django :: 3.2",
37
+ "Framework :: Django :: 4.0",
38
+ "Framework :: Django :: 4.1",
39
+ "Framework :: Django :: 4.2",
40
+ "Framework :: Django :: 5.0",
41
+ "Framework :: Django :: 5.1",
42
+ "Intended Audience :: Developers",
43
+ "License :: OSI Approved :: MIT License",
44
+ "Operating System :: OS Independent",
45
+ "Programming Language :: Python",
46
+ "Programming Language :: Python :: 3",
47
+ "Programming Language :: Python :: 3.8",
48
+ "Programming Language :: Python :: 3.9",
49
+ "Programming Language :: Python :: 3.10",
50
+ "Programming Language :: Python :: 3.11",
51
+ "Programming Language :: Python :: 3.12",
52
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
53
+ "Topic :: Software Development :: Libraries :: Python Modules",
54
+ ]
55
+ requires-python = ">=3.8"
56
+ dependencies = [
57
+ "Django>=3.2",
58
+ ]
59
+
60
+ [project.urls]
61
+ Homepage = "https://github.com/nkscoder/coupons"
62
+ Documentation = "https://github.com/nkscoder/coupons#readme"
63
+ Repository = "https://github.com/nkscoder/coupons"
64
+ "Bug Tracker" = "https://github.com/nkscoder/coupons/issues"
65
+ "Author GitHub" = "https://github.com/nkscoder"
66
+
67
+ [tool.setuptools.packages.find]
68
+ include = ["coupons*"]
69
+
70
+ [tool.setuptools.package-data]
71
+ coupons = ["migrations/*.py"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+