🤬
  • ■ ■ ■ ■ ■ ■
    .env.default
     1 +DEBUG=true
     2 +# DATABASE_URL=
     3 + 
  • ■ ■ ■ ■ ■ ■
    .gitignore
     1 +### Python template
     2 +# Byte-compiled / optimized / DLL files
     3 +__pycache__/
     4 +*.py[cod]
     5 +*$py.class
     6 + 
     7 +# C extensions
     8 +*.so
     9 + 
     10 +# Distribution / packaging
     11 +.Python
     12 +build/
     13 +develop-eggs/
     14 +dist/
     15 +eggs/
     16 +.eggs/
     17 +sdist/
     18 +wheels/
     19 +*.egg-info/
     20 +.installed.cfg
     21 +*.egg
     22 + 
     23 +# PyInstaller
     24 +# Usually these files are written by a python script from a template
     25 +# before PyInstaller builds the exe, so as to inject date/other infos into it.
     26 +*.manifest
     27 +*.spec
     28 + 
     29 +# Installer logs
     30 +pip-log.txt
     31 +pip-delete-this-directory.txt
     32 + 
     33 +# Unit test / coverage reports
     34 +htmlcov/
     35 +.tox/
     36 +.coverage
     37 +.coverage.*
     38 +.cache
     39 +nosetests.xml
     40 +coverage.xml
     41 +*.cover
     42 +.hypothesis/
     43 + 
     44 +# Translations
     45 +*.mo
     46 +*.pot
     47 + 
     48 +# Django stuff:
     49 +staticfiles/
     50 + 
     51 +# Sphinx documentation
     52 +docs/_build/
     53 + 
     54 +# PyBuilder
     55 +target/
     56 + 
     57 +# pyenv
     58 +.python-version
     59 + 
     60 +# Environments
     61 +.venv
     62 +venv/
     63 +ENV/
     64 + 
     65 +# Rope project settings
     66 +.ropeproject
     67 + 
     68 +# mypy
     69 +.mypy_cache/
     70 + 
     71 + 
     72 +### Node template
     73 +# Logs
     74 +*.log
     75 +npm-debug.log*
     76 +yarn-debug.log*
     77 +yarn-error.log*
     78 + 
     79 +# Runtime data
     80 +pids
     81 +*.pid
     82 +*.seed
     83 +*.pid.lock
     84 + 
     85 +# nyc test coverage
     86 +.nyc_output
     87 + 
     88 +# Bower dependency directory (https://bower.io/)
     89 +bower_components
     90 + 
     91 +# Dependency directories
     92 +node_modules/
     93 +jspm_packages/
     94 + 
     95 +# Optional npm cache directory
     96 +.npm
     97 + 
     98 +# Optional eslint cache
     99 +.eslintcache
     100 + 
     101 +# Optional REPL history
     102 +.node_repl_history
     103 + 
     104 +# Output of 'npm pack'
     105 +*.tgz
     106 + 
     107 +# Yarn Integrity file
     108 +.yarn-integrity
     109 + 
     110 + 
     111 +### Linux template
     112 +*~
     113 + 
     114 +# temporary files which can be created if a process still has a handle open of a deleted file
     115 +.fuse_hidden*
     116 + 
     117 +# KDE directory preferences
     118 +.directory
     119 + 
     120 +# Linux trash folder which might appear on any partition or disk
     121 +.Trash-*
     122 + 
     123 +# .nfs files are created when an open file is removed but is still being accessed
     124 +.nfs*
     125 + 
     126 + 
     127 +### VisualStudioCode template
     128 +.vscode
     129 + 
     130 +# User-specific stuff:
     131 +.idea/
     132 + 
     133 +# CMake
     134 +cmake-build-debug/
     135 + 
     136 +## File-based project format:
     137 +*.iws
     138 + 
     139 +# mpeltonen/sbt-idea plugin
     140 +.idea_modules/
     141 + 
     142 +# JIRA plugin
     143 +atlassian-ide-plugin.xml
     144 + 
     145 +# Crashlytics plugin (for Android Studio and IntelliJ)
     146 +com_crashlytics_export_strings.xml
     147 +crashlytics.properties
     148 +crashlytics-build.properties
     149 +fabric.properties
     150 + 
     151 + 
     152 +### Windows template
     153 +# Windows thumbnail cache files
     154 +Thumbs.db
     155 +ehthumbs.db
     156 +ehthumbs_vista.db
     157 + 
     158 +# Dump file
     159 +*.stackdump
     160 + 
     161 +# Folder config file
     162 +Desktop.ini
     163 + 
     164 +# Recycle Bin used on file shares
     165 +$RECYCLE.BIN/
     166 + 
     167 +# Windows Installer files
     168 +*.cab
     169 +*.msi
     170 +*.msm
     171 +*.msp
     172 + 
     173 +# Windows shortcuts
     174 +*.lnk
     175 + 
     176 + 
     177 +### macOS template
     178 +# General
     179 +*.DS_Store
     180 +.AppleDouble
     181 +.LSOverride
     182 + 
     183 +# Icon must end with two \r
     184 +Icon
     185 + 
     186 +# Thumbnails
     187 +._*
     188 + 
     189 +# Files that might appear in the root of a volume
     190 +.DocumentRevisions-V100
     191 +.fseventsd
     192 +.Spotlight-V100
     193 +.TemporaryItems
     194 +.Trashes
     195 +.VolumeIcon.icns
     196 +.com.apple.timemachine.donotpresent
     197 + 
     198 +# Directories potentially created on remote AFP share
     199 +.AppleDB
     200 +.AppleDesktop
     201 +Network Trash Folder
     202 +Temporary Items
     203 +.apdisk
     204 + 
     205 + 
     206 +### SublimeText template
     207 +# Cache files for Sublime Text
     208 +*.tmlanguage.cache
     209 +*.tmPreferences.cache
     210 +*.stTheme.cache
     211 + 
     212 +# Workspace files are user-specific
     213 +*.sublime-workspace
     214 + 
     215 +# Project files should be checked into the repository, unless a significant
     216 +# proportion of contributors will probably not be using Sublime Text
     217 +# *.sublime-project
     218 + 
     219 +# SFTP configuration file
     220 +sftp-config.json
     221 + 
     222 +# Package control specific files
     223 +Package Control.last-run
     224 +Package Control.ca-list
     225 +Package Control.ca-bundle
     226 +Package Control.system-ca-bundle
     227 +Package Control.cache/
     228 +Package Control.ca-certs/
     229 +Package Control.merged-ca-bundle
     230 +Package Control.user-ca-bundle
     231 +oscrypto-ca-bundle.crt
     232 +bh_unicode_properties.cache
     233 + 
     234 +# Sublime-github package stores a github token in this file
     235 +# https://packagecontrol.io/packages/sublime-github
     236 +GitHub.sublime-settings
     237 + 
     238 + 
     239 +### Vim template
     240 +# Swap
     241 +[._]*.s[a-v][a-z]
     242 +[._]*.sw[a-p]
     243 +[._]s[a-v][a-z]
     244 +[._]sw[a-p]
     245 + 
     246 +# Session
     247 +Session.vim
     248 + 
     249 +# Temporary
     250 +.netrwhist
     251 + 
     252 +db.sqlite3
     253 +/data/
     254 + 
     255 +.pytest_cache/
     256 +.env
     257 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/__init__.py
     1 +default_app_config = 'app.ucenter.apps.UcenterConfig'
     2 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/admin.py
     1 +from django.contrib import admin
     2 +from django.contrib.auth.admin import UserAdmin
     3 +from django.utils.translation import gettext, gettext_lazy as _
     4 + 
     5 +from . import models
     6 + 
     7 + 
     8 +@admin.register(models.User)
     9 +class MyUserAdmin(UserAdmin):
     10 + fieldsets = (
     11 + (None, {'fields': ('username', 'email', 'password', 'money')}),
     12 + (_('Permissions'), {'fields': ('is_staff', 'is_superuser', 'user_permissions')}),
     13 + (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
     14 + )
     15 + list_display = ('username', 'email', 'is_active', 'is_staff', 'is_superuser', 'money', 'last_login', 'date_joined')
     16 + readonly_fields = ('last_login', 'date_joined',)
     17 + list_filter = ('is_staff', 'is_active', 'is_superuser')
     18 + search_fields = ('username', 'email', )
     19 + ordering = ('-date_joined', )
     20 + 
     21 + 
     22 +@admin.register(models.WithdrawLog)
     23 +class WithdrawAdmin(admin.ModelAdmin):
     24 + fieldsets = (
     25 + (None, {'fields': ('amount', 'user')}),
     26 + (_('Important dates'), {'fields': ('created_time', 'last_modify_time')}),
     27 + )
     28 + list_display = ('created_time', 'user', 'amount')
     29 + readonly_fields = ('created_time', 'last_modify_time')
     30 + ordering = ('-created_time',)
     31 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/apps.py
     1 +from django.apps import AppConfig
     2 + 
     3 + 
     4 +class UcenterConfig(AppConfig):
     5 + name = 'app.ucenter'
     6 + label = 'ucenter'
     7 + verbose_name = 'User Center'
     8 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/forms.py
     1 +from django.shortcuts import get_object_or_404
     2 +from django import forms
     3 + 
     4 +from . import models
     5 + 
     6 + 
     7 +class WithdrawForm(forms.Form):
     8 + amount = forms.IntegerField(min_value=1)
     9 + 
     10 + def __init__(self, *args, **kwargs):
     11 + self.user = kwargs.pop('user', None)
     12 + super().__init__(*args, **kwargs)
     13 + 
     14 + def clean_amount(self):
     15 + amount = self.cleaned_data['amount']
     16 + if amount > self.user.money:
     17 + raise forms.ValidationError('insufficient user balance')
     18 + 
     19 + return amount
     20 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/migrations/0001_initial.py
     1 +# Generated by Django 3.1.1 on 2020-09-23 09:56
     2 + 
     3 +import django.contrib.auth.models
     4 +from django.db import migrations, models
     5 +import django.utils.timezone
     6 + 
     7 + 
     8 +class Migration(migrations.Migration):
     9 + 
     10 + initial = True
     11 + 
     12 + dependencies = [
     13 + ('auth', '0012_alter_user_first_name_max_length'),
     14 + ]
     15 + 
     16 + operations = [
     17 + migrations.CreateModel(
     18 + name='User',
     19 + fields=[
     20 + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
     21 + ('password', models.CharField(max_length=128, verbose_name='password')),
     22 + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
     23 + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
     24 + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
     25 + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
     26 + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
     27 + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
     28 + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
     29 + ('username', models.CharField(max_length=256, verbose_name='username')),
     30 + ('email', models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email')),
     31 + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
     32 + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
     33 + ],
     34 + options={
     35 + 'verbose_name': 'user',
     36 + 'verbose_name_plural': 'user',
     37 + 'abstract': False,
     38 + 'swappable': 'AUTH_USER_MODEL',
     39 + },
     40 + managers=[
     41 + ('objects', django.contrib.auth.models.UserManager()),
     42 + ],
     43 + ),
     44 + ]
     45 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/migrations/0002_product_user_money_alter_user_id.py
     1 +# Generated by Django 4.1.7 on 2023-03-18 06:17
     2 + 
     3 +from django.db import migrations, models
     4 + 
     5 + 
     6 +class Migration(migrations.Migration):
     7 + 
     8 + dependencies = [
     9 + ('ucenter', '0001_initial'),
     10 + ]
     11 + 
     12 + operations = [
     13 + migrations.AddField(
     14 + model_name='user',
     15 + name='money',
     16 + field=models.IntegerField(default=0, verbose_name='money'),
     17 + ),
     18 + migrations.AlterField(
     19 + model_name='user',
     20 + name='id',
     21 + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
     22 + ),
     23 + ]
     24 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/migrations/0003_withdrawlog.py
     1 +# Generated by Django 4.1.7 on 2023-03-18 07:05
     2 + 
     3 +from django.conf import settings
     4 +from django.db import migrations, models
     5 +import django.db.models.deletion
     6 + 
     7 + 
     8 +class Migration(migrations.Migration):
     9 + 
     10 + dependencies = [
     11 + ('ucenter', '0002_product_user_money_alter_user_id'),
     12 + ]
     13 + 
     14 + operations = [
     15 + migrations.CreateModel(
     16 + name='WithdrawLog',
     17 + fields=[
     18 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
     19 + ('amount', models.IntegerField(verbose_name='amount')),
     20 + ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='created time')),
     21 + ('last_modify_time', models.DateTimeField(auto_now=True, verbose_name='last modify time')),
     22 + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user')),
     23 + ],
     24 + options={
     25 + 'verbose_name': 'withdraw log',
     26 + 'verbose_name_plural': 'withdraw logs',
     27 + },
     28 + ),
     29 + ]
     30 + 
  • ■ ■ ■ ■ ■
    app/ucenter/migrations/__init__.py
     1 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/models.py
     1 +from django.db import models
     2 +from django.contrib.auth.models import AbstractUser
     3 + 
     4 + 
     5 +class User(AbstractUser):
     6 + username = models.CharField('username', max_length=256)
     7 + email = models.EmailField('email', blank=True, unique=True)
     8 + money = models.IntegerField('money', default=0)
     9 + 
     10 + USERNAME_FIELD = 'email'
     11 + REQUIRED_FIELDS = ['username']
     12 + 
     13 + class Meta(AbstractUser.Meta):
     14 + swappable = 'AUTH_USER_MODEL'
     15 + verbose_name = 'user'
     16 + verbose_name_plural = verbose_name
     17 + 
     18 + def __str__(self):
     19 + return self.username
     20 + 
     21 + 
     22 +class WithdrawLog(models.Model):
     23 + user = models.ForeignKey('User', verbose_name='user', on_delete=models.SET_NULL, null=True)
     24 + amount = models.IntegerField('amount')
     25 + 
     26 + created_time = models.DateTimeField('created time', auto_now_add=True)
     27 + last_modify_time = models.DateTimeField('last modify time', auto_now=True)
     28 + 
     29 + class Meta:
     30 + verbose_name = 'withdraw log'
     31 + verbose_name_plural = 'withdraw logs'
     32 + 
     33 + def __str__(self):
     34 + return str(self.created_time)
     35 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/tests.py
     1 +from django.test import TestCase
     2 + 
     3 +# Create your tests here.
     4 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/urls.py
     1 +from django.urls import path, include
     2 +from . import views
     3 + 
     4 + 
     5 +app_name = 'ucenter'
     6 +urlpatterns = [
     7 + path('1/', views.WithdrawView1.as_view(), name='withdraw1'),
     8 + path('2/', views.WithdrawView2.as_view(), name='withdraw2'),
     9 + path('3/', views.WithdrawView3.as_view(), name='withdraw3'),
     10 + path('4/', views.WithdrawView3.as_view(), name='withdraw4'),
     11 +]
     12 + 
  • ■ ■ ■ ■ ■ ■
    app/ucenter/views.py
     1 +from django.shortcuts import render, get_object_or_404, redirect
     2 +from django.urls import reverse_lazy
     3 +from django.views import generic
     4 +from django.contrib.auth.mixins import LoginRequiredMixin
     5 +from django.db import transaction
     6 +from django.db.models import F
     7 + 
     8 +from . import models, forms
     9 + 
     10 +class BaseWithdrawView(LoginRequiredMixin, generic.FormView):
     11 + template_name = 'form.html'
     12 + form_class = forms.WithdrawForm
     13 + 
     14 + def get_form_kwargs(self):
     15 + kwargs = super().get_form_kwargs()
     16 + kwargs['user'] = self.request.user
     17 + return kwargs
     18 + 
     19 + 
     20 +class WithdrawView1(BaseWithdrawView):
     21 + success_url = reverse_lazy('ucenter:withdraw1')
     22 + 
     23 + def form_valid(self, form):
     24 + amount = form.cleaned_data['amount']
     25 + self.request.user.money -= amount
     26 + self.request.user.save()
     27 + models.WithdrawLog.objects.create(user=self.request.user, amount=amount)
     28 + 
     29 + return redirect(self.get_success_url())
     30 + 
     31 + 
     32 +class WithdrawView2(BaseWithdrawView):
     33 + success_url = reverse_lazy('ucenter:withdraw2')
     34 + 
     35 + @transaction.atomic
     36 + def form_valid(self, form):
     37 + amount = form.cleaned_data['amount']
     38 + self.request.user.money -= amount
     39 + self.request.user.save()
     40 + models.WithdrawLog.objects.create(user=self.request.user, amount=amount)
     41 + 
     42 + return redirect(self.get_success_url())
     43 + 
     44 + 
     45 +class WithdrawView3(BaseWithdrawView):
     46 + success_url = reverse_lazy('ucenter:withdraw3')
     47 + 
     48 + def get_form_kwargs(self):
     49 + kwargs = super().get_form_kwargs()
     50 + kwargs['user'] = self.user
     51 + return kwargs
     52 + 
     53 + @transaction.atomic
     54 + def dispatch(self, request, *args, **kwargs):
     55 + self.user = get_object_or_404(models.User.objects.select_for_update().all(), pk=self.request.user.pk)
     56 + return super().dispatch(request, *args, **kwargs)
     57 + 
     58 + def form_valid(self, form):
     59 + amount = form.cleaned_data['amount']
     60 + self.user.money -= amount
     61 + self.user.save()
     62 + models.WithdrawLog.objects.create(user=self.user, amount=amount)
     63 + 
     64 + return redirect(self.get_success_url())
     65 + 
     66 + 
     67 +class WithdrawView4(BaseWithdrawView):
     68 + success_url = reverse_lazy('ucenter:withdraw4')
     69 + 
     70 + @transaction.atomic
     71 + def form_valid(self, form):
     72 + amount = form.cleaned_data['amount']
     73 + rows = models.User.objects.filter(pk=self.request.user, money__gte=amount).update(money=F('money')-amount)
     74 + if rows > 0:
     75 + models.WithdrawLog.objects.create(user=self.request.user, amount=amount)
     76 + 
     77 + return redirect(self.get_success_url())
     78 + 
  • ■ ■ ■ ■ ■ ■
    bin/web.sh
     1 +#!/bin/bash
     2 + 
     3 +set -eo pipefail
     4 + 
     5 +dirs=('cache' 'logs' 'media' 'sqlite3' 'statiic')
     6 +for dir in "${dirs[@]}"; do
     7 + mkdir -p "data/${dir}"
     8 +done
     9 + 
     10 +./manage.py collectstatic --no-input
     11 +./manage.py migrate --no-input
     12 + 
     13 +for dir in "${dirs[@]}"; do
     14 + chown -R nobody:nogroup "data/${dir}"
     15 +done
     16 + 
     17 +gunicorn -w 2 -k gevent -u nobody -g nogroup -b 0.0.0.0:8080 race_condition_playground.wsgi
     18 + 
  • ■ ■ ■ ■ ■ ■
    manage.py
     1 +#!/usr/bin/env python
     2 +"""Django's command-line utility for administrative tasks."""
     3 +import os
     4 +import sys
     5 +from dotenv import load_dotenv
     6 + 
     7 + 
     8 +def prepare_data_directory():
     9 + base = os.path.join(os.path.dirname(__file__), 'data')
     10 + os.makedirs(os.path.join(base, 'cache'), exist_ok=True)
     11 + os.makedirs(os.path.join(base, 'logs'), exist_ok=True)
     12 + os.makedirs(os.path.join(base, 'media'), exist_ok=True)
     13 + os.makedirs(os.path.join(base, 'sqlite3'), exist_ok=True)
     14 + os.makedirs(os.path.join(base, 'static'), exist_ok=True)
     15 + os.makedirs(os.path.join(base, 'postgres'), exist_ok=True)
     16 + 
     17 + 
     18 +def main():
     19 + dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
     20 + dotenv_path and load_dotenv(dotenv_path)
     21 + prepare_data_directory()
     22 + 
     23 + """Run administrative tasks."""
     24 + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'race_condition_playground.settings')
     25 + try:
     26 + from django.core.management import execute_from_command_line
     27 + except ImportError as exc:
     28 + raise ImportError(
     29 + "Couldn't import Django. Are you sure it's installed and "
     30 + "available on your PYTHONPATH environment variable? Did you "
     31 + "forget to activate a virtual environment?"
     32 + ) from exc
     33 + execute_from_command_line(sys.argv)
     34 + 
     35 + 
     36 +if __name__ == '__main__':
     37 + main()
     38 + 
  • ■ ■ ■ ■ ■
    race_condition_playground/__init__.py
     1 + 
  • ■ ■ ■ ■ ■ ■
    race_condition_playground/asgi.py
     1 +"""
     2 +ASGI config for race_condition_playground project.
     3 + 
     4 +It exposes the ASGI callable as a module-level variable named ``application``.
     5 + 
     6 +For more information on this file, see
     7 +https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
     8 +"""
     9 + 
     10 +import os
     11 +from dotenv import load_dotenv
     12 + 
     13 +from django.core.asgi import get_asgi_application
     14 + 
     15 +dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
     16 +dotenv_path and load_dotenv(dotenv_path)
     17 +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'race_condition_playground.settings')
     18 + 
     19 +application = get_asgi_application()
     20 + 
  • ■ ■ ■ ■ ■ ■
    race_condition_playground/settings.py
     1 +"""
     2 +Django settings for race_condition_playground project.
     3 + 
     4 +Generated by 'django-admin startproject' using Django 3.1.1.
     5 + 
     6 +For more information on this file, see
     7 +https://docs.djangoproject.com/en/3.1/topics/settings/
     8 + 
     9 +For the full list of settings and their values, see
     10 +https://docs.djangoproject.com/en/3.1/ref/settings/
     11 +"""
     12 + 
     13 +import os
     14 +import dj_database_url
     15 +from pathlib import Path
     16 + 
     17 +PROJECT_SLUG = 'race_condition_playground'
     18 +# Build paths inside the project like this: BASE_DIR / 'subdir'.
     19 +BASE_DIR = Path(__file__).resolve().parent.parent
     20 + 
     21 + 
     22 +# Quick-start development settings - unsuitable for production
     23 +# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
     24 + 
     25 +# SECURITY WARNING: keep the secret key used in production secret!
     26 +SECRET_KEY = os.environ.get('SECRET_KEY', 'secret')
     27 + 
     28 +# SECURITY WARNING: don't run with debug turned on in production!
     29 +DEBUG = os.environ.get('DEBUG') in ('true', 'True', '1', 'TRUE', 'on', 'ON')
     30 + 
     31 +ALLOWED_HOSTS = ['*']
     32 + 
     33 + 
     34 +# Application definition
     35 + 
     36 +INSTALLED_APPS = [
     37 + 'django.contrib.admin',
     38 + 'django.contrib.auth',
     39 + 'django.contrib.contenttypes',
     40 + 'django.contrib.sessions',
     41 + 'django.contrib.messages',
     42 + 'django.contrib.staticfiles',
     43 + 'django_bootstrap5',
     44 + 'app.ucenter',
     45 +]
     46 + 
     47 +MIDDLEWARE = [
     48 + 'django.middleware.security.SecurityMiddleware',
     49 + 'django.contrib.sessions.middleware.SessionMiddleware',
     50 + 'django.middleware.common.CommonMiddleware',
     51 + 'django.middleware.csrf.CsrfViewMiddleware',
     52 + 'django.contrib.auth.middleware.AuthenticationMiddleware',
     53 + 'django.contrib.messages.middleware.MessageMiddleware',
     54 + 'django.middleware.clickjacking.XFrameOptionsMiddleware',
     55 +]
     56 + 
     57 +ROOT_URLCONF = PROJECT_SLUG + '.urls'
     58 + 
     59 +TEMPLATES = [
     60 + {
     61 + 'BACKEND': 'django.template.backends.django.DjangoTemplates',
     62 + 'DIRS': [
     63 + BASE_DIR / 'templates'
     64 + ],
     65 + 'APP_DIRS': True,
     66 + 'OPTIONS': {
     67 + 'context_processors': [
     68 + 'django.template.context_processors.debug',
     69 + 'django.template.context_processors.request',
     70 + 'django.contrib.auth.context_processors.auth',
     71 + 'django.contrib.messages.context_processors.messages',
     72 + ],
     73 + },
     74 + },
     75 +]
     76 + 
     77 +WSGI_APPLICATION = PROJECT_SLUG + '.wsgi.application'
     78 + 
     79 +AUTH_USER_MODEL = 'ucenter.User'
     80 + 
     81 +# Database
     82 +# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
     83 + 
     84 +DATABASES = {
     85 + 'default': dj_database_url.config(default='sqlite:///' + str(BASE_DIR / 'data' / 'sqlite3' / 'db.sqlite3'))
     86 +}
     87 + 
     88 + 
     89 +# Password validation
     90 +# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
     91 + 
     92 +AUTH_PASSWORD_VALIDATORS = [
     93 + {
     94 + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
     95 + },
     96 + {
     97 + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
     98 + },
     99 + {
     100 + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
     101 + },
     102 + {
     103 + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
     104 + },
     105 +]
     106 + 
     107 + 
     108 +# Internationalization
     109 +# https://docs.djangoproject.com/en/3.1/topics/i18n/
     110 + 
     111 +LANGUAGE_CODE = 'en-us'
     112 + 
     113 +TIME_ZONE = 'Asia/Singapore'
     114 + 
     115 +USE_I18N = True
     116 + 
     117 +USE_L10N = True
     118 + 
     119 +USE_TZ = True
     120 + 
     121 +CACHES = {
     122 + 'default': {
     123 + 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
     124 + 'LOCATION': BASE_DIR / 'data' / 'cache',
     125 + }
     126 +}
     127 + 
     128 +LOGGING = {
     129 + 'version': 1,
     130 + 'disable_existing_loggers': False,
     131 + 'formatters': {
     132 + 'standard': {
     133 + 'format': '[%(asctime)s] - [%(levelname)s] - [%(pathname)s:%(lineno)d] - %(message)s',
     134 + 'datefmt': '%Y-%m-%d %H:%M:%S'
     135 + },
     136 + 'sample': {
     137 + 'format': '[%(asctime)s] - %(message)s',
     138 + 'datefmt': '%Y-%m-%d %H:%M:%S'
     139 + },
     140 + },
     141 + 'handlers': {
     142 + 'console': {
     143 + 'level': 'DEBUG',
     144 + 'class': 'logging.StreamHandler',
     145 + 'formatter': 'standard',
     146 + 'filters': ['discard_not_found_error'],
     147 + },
     148 + 'file': {
     149 + 'level': 'DEBUG',
     150 + 'class': 'logging.FileHandler',
     151 + 'filename': BASE_DIR / 'data' / 'logs' / 'django.log',
     152 + 'filters': ['discard_not_found_error'],
     153 + 'formatter': 'standard'
     154 + }
     155 + },
     156 + 'loggers': {
     157 + '': {
     158 + 'handlers': ['console', 'file'],
     159 + 'level': 'WARNING'
     160 + },
     161 + 'django': {
     162 + 'handlers': ['console', 'file'],
     163 + 'level': 'WARNING',
     164 + 'propagate': False
     165 + },
     166 + },
     167 + 'filters': {
     168 + 'discard_not_found_error': {
     169 + '()': 'django.utils.log.CallbackFilter',
     170 + 'callback': lambda record: (not hasattr(record, 'status_code')) or
     171 + (hasattr(record, 'status_code') and not (400 <= record.status_code < 500)),
     172 + }
     173 + },
     174 +}
     175 + 
     176 +# Static files (CSS, JavaScript, Images)
     177 +# https://docs.djangoproject.com/en/3.1/howto/static-files/
     178 + 
     179 +STATIC_URL = '/static/'
     180 +STATIC_ROOT = BASE_DIR / 'data' / 'static'
     181 +MEDIA_URL = '/media/'
     182 +MEDIA_ROOT = BASE_DIR / 'data' / 'media'
     183 + 
     184 + 
     185 +SESSION_COOKIE_AGE = 60 * 60 * 24 * 180
     186 +SECURE_REFERRER_POLICY = 'origin-when-cross-origin'
     187 +# SESSION_COOKIE_SECURE = not DEBUG
     188 +# CSRF_COOKIE_SECURE = not DEBUG
     189 +# SECURE_SSL_REDIRECT = not DEBUG
     190 +# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
     191 +X_FRAME_OPTIONS = 'deny'
     192 + 
     193 +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
     194 + 
  • ■ ■ ■ ■ ■ ■
    race_condition_playground/urls.py
     1 +"""race_condition_playground URL Configuration
     2 + 
     3 +The `urlpatterns` list routes URLs to views. For more information please see:
     4 + https://docs.djangoproject.com/en/3.1/topics/http/urls/
     5 +Examples:
     6 +Function views
     7 + 1. Add an import: from my_app import views
     8 + 2. Add a URL to urlpatterns: path('', views.home, name='home')
     9 +Class-based views
     10 + 1. Add an import: from other_app.views import Home
     11 + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
     12 +Including another URLconf
     13 + 1. Import the include() function: from django.urls import include, path
     14 + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
     15 +"""
     16 +from django.contrib import admin
     17 +from django.urls import path, include
     18 + 
     19 +urlpatterns = [
     20 + path('admin/', admin.site.urls),
     21 + path('ucenter/', include('app.ucenter.urls', namespace='ucenter')),
     22 +]
     23 + 
  • ■ ■ ■ ■ ■ ■
    race_condition_playground/wsgi.py
     1 +"""
     2 +WSGI config for race_condition_playground project.
     3 + 
     4 +It exposes the WSGI callable as a module-level variable named ``application``.
     5 + 
     6 +For more information on this file, see
     7 +https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
     8 +"""
     9 + 
     10 +import os
     11 +from dotenv import load_dotenv
     12 + 
     13 +from django.core.wsgi import get_wsgi_application
     14 + 
     15 +dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
     16 +dotenv_path and load_dotenv(dotenv_path)
     17 +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'race_condition_playground.settings')
     18 + 
     19 +application = get_wsgi_application()
     20 + 
  • ■ ■ ■ ■ ■ ■
    requirements.txt
     1 +django
     2 +pytz
     3 +python-dotenv
     4 +dj-database-url
     5 +psycopg2-binary
     6 +gunicorn
     7 +gevent
     8 +django-bootstrap5
     9 +waitress
     10 + 
  • ■ ■ ■ ■ ■ ■
    templates/form.html
     1 +{% load django_bootstrap5 %}
     2 + 
     3 +<!DOCTYPE html>
     4 +<html lang="en">
     5 +<head>
     6 + <meta charset="UTF-8">
     7 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
     8 + <meta http-equiv="X-UA-Compatible" content="ie=edge">
     9 + <title>Withdraw money</title>
     10 + <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
     11 +</head>
     12 +<body>
     13 + <div class="container mt-5">
     14 + <h1>Withdraw money</h1>
     15 + <hr>
     16 + <form method="post">
     17 + <div class="form-group">
     18 + <label for="username">Username</label>
     19 + <input type="text" class="form-control" value="{{ request.user.username }}" readonly>
     20 + </div>
     21 + <div class="form-group">
     22 + <label for="balance">Money</label>
     23 + <input type="text" class="form-control" value="{{ request.user.money }}" readonly>
     24 + </div>
     25 + {% bootstrap_form form %}
     26 + <button type="submit" class="btn btn-primary">提交</button>
     27 + {% csrf_token %}
     28 + </form>
     29 + </div>
     30 +</body>
     31 +</html>
     32 + 
Please wait...
Page is in error, reload to recover