Skip to content

Commit 2c8df81

Browse files
committed
Use feature flag for otp and add indexes
1 parent 3641449 commit 2c8df81

File tree

17 files changed

+281
-142
lines changed

17 files changed

+281
-142
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ migrate:
88
@echo "Migrating..."
99
python manage.py migrate
1010

11-
create_migration:
11+
migrations:
1212
@echo "Creating migration..."
1313
python manage.py makemigrations

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
- 3 Failed attempts and block user for 24 hours.
55
- Throttle OTP generation
66
- OTP support via third party services
7+
- Support multiple offers with expiry and use the latest offer to give points

accounts/admin.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
from django.contrib import admin
22

3+
from accounts.models import User, OTPValidation
4+
5+
36
# Register your models here.
7+
@admin.register(User)
8+
class UserAdmin(admin.ModelAdmin):
9+
list_display = ("username", "email", "is_staff", "is_active", "is_superuser", "created_at")
10+
list_filter = ("is_active", "is_superuser")
11+
search_fields = ("username",)
12+
13+
14+
@admin.register(OTPValidation)
15+
class OTPValidationAdmin(admin.ModelAdmin):
16+
list_display = ("destination", "otp", "valid_until", "is_validated")
17+
list_filter = ("is_validated",)
18+
search_fields = ("destination",)
19+
Lines changed: 92 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.1.5 on 2023-01-16 09:13
1+
# Generated by Django 4.1.5 on 2023-01-22 18:13
22

33
import django.contrib.auth.models
44
from django.db import migrations, models
@@ -16,8 +16,23 @@ class Migration(migrations.Migration):
1616

1717
operations = [
1818
migrations.CreateModel(
19-
name="OTPValidation",
19+
name="User",
2020
fields=[
21+
("password", models.CharField(max_length=128, verbose_name="password")),
22+
(
23+
"last_login",
24+
models.DateTimeField(
25+
blank=True, null=True, verbose_name="last login"
26+
),
27+
),
28+
(
29+
"is_superuser",
30+
models.BooleanField(
31+
default=False,
32+
help_text="Designates that this user has all permissions without explicitly assigning them.",
33+
verbose_name="superuser status",
34+
),
35+
),
2136
(
2237
"id",
2338
models.UUIDField(
@@ -30,57 +45,28 @@ class Migration(migrations.Migration):
3045
("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
3146
("modified_at", models.DateTimeField(auto_now=True)),
3247
(
33-
"otp",
34-
models.CharField(blank=True, max_length=6, null=True, verbose_name="OTP"),
35-
),
36-
(
37-
"valid_until",
38-
models.DateTimeField(
39-
default=django.utils.timezone.now,
40-
help_text="The timestamp of the moment of expiry of the saved token.",
41-
),
42-
),
43-
(
44-
"destination",
48+
"username",
4549
models.CharField(
46-
db_index=True,
47-
max_length=10,
48-
unique=True,
49-
verbose_name="OTP Generated For",
50+
max_length=10, unique=True, verbose_name="Mobile Number"
5051
),
5152
),
52-
("is_validated", models.BooleanField(default=False)),
53-
(
54-
"validate_attempt",
55-
models.IntegerField(default=3, verbose_name="Attempted Validation"),
56-
),
57-
(
58-
"otp_reactive_at",
59-
models.DateTimeField(blank=True, null=True, verbose_name="OTP Reactive At"),
60-
),
61-
("extra_data", models.JSONField(blank=True, null=True)),
53+
("email", models.EmailField(blank=True, max_length=255, null=True)),
54+
("name", models.CharField(blank=True, max_length=255, null=True)),
55+
("is_active", models.BooleanField(default=True)),
56+
("is_staff", models.BooleanField(default=False)),
6257
],
6358
options={
64-
"ordering": ["-created_at"],
65-
"abstract": False,
59+
"verbose_name": "User",
60+
"verbose_name_plural": "Users",
61+
"ordering": ("-created_at",),
6662
},
63+
managers=[
64+
("objects", django.contrib.auth.models.UserManager()),
65+
],
6766
),
6867
migrations.CreateModel(
69-
name="User",
68+
name="OTPValidation",
7069
fields=[
71-
("password", models.CharField(max_length=128, verbose_name="password")),
72-
(
73-
"last_login",
74-
models.DateTimeField(blank=True, null=True, verbose_name="last login"),
75-
),
76-
(
77-
"is_superuser",
78-
models.BooleanField(
79-
default=False,
80-
help_text="Designates that this user has all permissions without explicitly assigning them.",
81-
verbose_name="superuser status",
82-
),
83-
),
8470
(
8571
"id",
8672
models.UUIDField(
@@ -93,42 +79,77 @@ class Migration(migrations.Migration):
9379
("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
9480
("modified_at", models.DateTimeField(auto_now=True)),
9581
(
96-
"username",
97-
models.CharField(max_length=10, unique=True, verbose_name="Mobile Number"),
82+
"otp",
83+
models.CharField(
84+
blank=True, max_length=6, null=True, verbose_name="OTP"
85+
),
86+
),
87+
(
88+
"valid_until",
89+
models.DateTimeField(
90+
default=django.utils.timezone.now,
91+
help_text="The timestamp of the moment of expiry of the saved token.",
92+
),
9893
),
99-
("email", models.EmailField(blank=True, max_length=255, null=True)),
100-
("name", models.CharField(blank=True, max_length=255, null=True)),
101-
("is_active", models.BooleanField(default=True)),
102-
("is_staff", models.BooleanField(default=False)),
10394
(
104-
"groups",
105-
models.ManyToManyField(
106-
blank=True,
107-
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
108-
related_name="user_set",
109-
related_query_name="user",
110-
to="auth.group",
111-
verbose_name="groups",
95+
"destination",
96+
models.CharField(
97+
max_length=10, unique=True, verbose_name="OTP Generated For"
11298
),
11399
),
100+
("is_validated", models.BooleanField(default=False)),
101+
(
102+
"validate_attempt",
103+
models.IntegerField(default=3, verbose_name="Attempted Validation"),
104+
),
114105
(
115-
"user_permissions",
116-
models.ManyToManyField(
117-
blank=True,
118-
help_text="Specific permissions for this user.",
119-
related_name="user_set",
120-
related_query_name="user",
121-
to="auth.permission",
122-
verbose_name="user permissions",
106+
"otp_reactive_at",
107+
models.DateTimeField(
108+
blank=True, null=True, verbose_name="OTP Reactive At"
123109
),
124110
),
111+
("extra_data", models.JSONField(blank=True, null=True)),
125112
],
126113
options={
127-
"verbose_name": "User",
128-
"verbose_name_plural": "Users",
114+
"verbose_name": "OTP Validation",
115+
"verbose_name_plural": "OTP Validations",
116+
"ordering": ["-created_at"],
129117
},
130-
managers=[
131-
("objects", django.contrib.auth.models.UserManager()),
132-
],
118+
),
119+
migrations.AddIndex(
120+
model_name="otpvalidation",
121+
index=models.Index(
122+
fields=["destination"], name="accounts_ot_destina_e42df1_idx"
123+
),
124+
),
125+
migrations.AddField(
126+
model_name="user",
127+
name="groups",
128+
field=models.ManyToManyField(
129+
blank=True,
130+
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
131+
related_name="user_set",
132+
related_query_name="user",
133+
to="auth.group",
134+
verbose_name="groups",
135+
),
136+
),
137+
migrations.AddField(
138+
model_name="user",
139+
name="user_permissions",
140+
field=models.ManyToManyField(
141+
blank=True,
142+
help_text="Specific permissions for this user.",
143+
related_name="user_set",
144+
related_query_name="user",
145+
to="auth.permission",
146+
verbose_name="user permissions",
147+
),
148+
),
149+
migrations.AddIndex(
150+
model_name="user",
151+
index=models.Index(
152+
fields=["username"], name="accounts_us_usernam_c0ea66_idx"
153+
),
133154
),
134155
]

accounts/models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def save(self, *args, **kwargs):
4040
class Meta:
4141
verbose_name = _("User")
4242
verbose_name_plural = _("Users")
43+
ordering = ("-created_at",)
44+
indexes = [models.Index(fields=["username"])]
4345

4446

4547
class OTPValidation(TimeStampedModel):
@@ -52,7 +54,6 @@ class OTPValidation(TimeStampedModel):
5254
destination = models.CharField(
5355
verbose_name=_("OTP Generated For"),
5456
max_length=10,
55-
db_index=True,
5657
unique=True,
5758
)
5859
is_validated = models.BooleanField(default=False)
@@ -64,6 +65,12 @@ class OTPValidation(TimeStampedModel):
6465
def __str__(self):
6566
return self.destination
6667

68+
class Meta:
69+
ordering = ["-created_at"]
70+
verbose_name = _("OTP Validation")
71+
verbose_name_plural = _("OTP Validations")
72+
indexes = [models.Index(fields=["destination"])]
73+
6774
def generate_otp(self, length=6, valid_secs=600) -> None:
6875
"""
6976
Generates a token of the specified length, then sets it on the model

core/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class RequestType:
2+
"""Request types for the API."""
3+
4+
GET = "GET"
5+
POST = "POST"
6+
PUT = "PUT"
7+
PATCH = "PATCH"
8+
DELETE = "DELETE"

core/flags.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from flags.state import flag_enabled
2+
3+
4+
class FlagSources:
5+
ENABLE_OTP: str = "ENABLE_OTP"
6+
7+
@classmethod
8+
def otp_enabled(cls) -> bool:
9+
return flag_enabled(cls.ENABLE_OTP)
10+
11+
def get_flags(self, sources=None):
12+
return {
13+
self.ENABLE_OTP: [],
14+
}

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ djangorestframework==3.14.0
88
pytailwindcss==0.1.4
99
requests==2.28.1
1010
pre-commit==2.21.0
11-
django-environ==0.9.0
11+
django-environ==0.9.0
12+
django-flags==5.0.12
13+
sentry-sdk==1.13.0

rewardme/settings.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"rest_framework",
6363
"django_extensions",
6464
"simple_history",
65+
"flags",
6566
]
6667

6768

@@ -144,7 +145,7 @@
144145

145146
LANGUAGE_CODE = "en-us"
146147

147-
TIME_ZONE = "UTC"
148+
TIME_ZONE = "Asia/Kolkata"
148149

149150
USE_I18N = True
150151

@@ -175,3 +176,23 @@
175176
# Simple History
176177
SIMPLE_HISTORY_HISTORY_ID_USE_UUID = True
177178
SIMPLE_HISTORY_HISTORY_CHANGE_REASON_USE_TEXT_FIELD = True
179+
180+
# Flags
181+
FLAG_SOURCES = (
182+
"core.flags.FlagSources",
183+
"flags.sources.SettingsFlagsSource",
184+
"flags.sources.DatabaseFlagsSource",
185+
)
186+
187+
# Sentry
188+
if USE_SENTRY := env.bool('USE_SENTRY', default=False):
189+
import sentry_sdk
190+
from sentry_sdk.integrations.django import DjangoIntegration
191+
192+
sentry_sdk.init(
193+
dsn=env('SENTRY_DSN'),
194+
integrations=[DjangoIntegration()],
195+
traces_sample_rate=env.float('SENTRY_TRACES_SAMPLE_RATE', default=0.1),
196+
send_default_pii=True,
197+
release=env('SENTRY_RELEASE'),
198+
)

sample.env

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ USE_SQLITE=True
77
DB_NAME=
88
DB_USER=
99
DB_PASSWORD=
10-
DB_HOST=
10+
DB_HOST=
11+
USE_SENTRY=
12+
SENTRY_DSN=
13+
SENTRY_SAMPLE_RATE=
14+
SENTRY_RELEASE

0 commit comments

Comments
 (0)