Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
9bc44bd65c |
2753 changed files with 24064 additions and 97991 deletions
17
.github/workflows/build.yml
vendored
Normal file
17
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install flake8
|
||||
run: pip install flake8 flake8-import-order flake8-future-import flake8-commas flake8-logging-format
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
flake8 --version
|
||||
flake8
|
40
.github/workflows/compilemessages.yml
vendored
Normal file
40
.github/workflows/compilemessages.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: compilemessages
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'locale/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'locale/**'
|
||||
jobs:
|
||||
compilemessages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Checkout submodules
|
||||
run: |
|
||||
git submodule init
|
||||
git submodule update
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get install gettext
|
||||
pip install -r requirements.txt
|
||||
pip install pymysql
|
||||
- name: Check .po file validity
|
||||
run: |
|
||||
fail=0
|
||||
while read -r file; do
|
||||
if ! msgfmt --check-format "$file"; then
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
done < <(find locale -name '*.po')
|
||||
exit "$fail"
|
||||
shell: bash
|
||||
- name: Compile messages
|
||||
run: |
|
||||
echo "STATIC_ROOT = '/tmp'" > dmoj/local_settings.py
|
||||
python manage.py compilemessages
|
33
.github/workflows/init.yml
vendored
33
.github/workflows/init.yml
vendored
|
@ -1,33 +0,0 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
# Trigger the workflow on push or pull request,
|
||||
# but only for the main branch
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
run-linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: pip install black
|
||||
|
||||
- name: Run linters
|
||||
uses: wearerequired/lint-action@v2
|
||||
with:
|
||||
black: true
|
53
.github/workflows/makemessages.yml
vendored
Normal file
53
.github/workflows/makemessages.yml
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
name: makemessages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
makemessages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Checkout submodules
|
||||
run: |
|
||||
git submodule init
|
||||
git submodule update
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get install gettext
|
||||
curl -O https://artifacts.crowdin.com/repo/deb/crowdin.deb
|
||||
sudo dpkg -i crowdin.deb
|
||||
pip install -r requirements.txt
|
||||
pip install pymysql
|
||||
- name: Collect localizable strings
|
||||
run: |
|
||||
echo "STATIC_ROOT = '/tmp'" > dmoj/local_settings.py
|
||||
python manage.py makemessages -l en -e py,html,txt
|
||||
python manage.py makemessages -l en -d djangojs
|
||||
- name: Upload strings to Crowdin
|
||||
env:
|
||||
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
|
||||
run: |
|
||||
cat > crowdin.yaml <<EOF
|
||||
project_identifier: dmoj
|
||||
|
||||
files:
|
||||
- source: /locale/en/LC_MESSAGES/django.po
|
||||
translation: /locale/%two_letters_code%/LC_MESSAGES/django.po
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh_Hans
|
||||
sr-CS: sr_Latn
|
||||
- source: /locale/en/LC_MESSAGES/djangojs.po
|
||||
translation: /locale/%two_letters_code%/LC_MESSAGES/djangojs.po
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh_Hans
|
||||
sr-CS: sr_Latn
|
||||
EOF
|
||||
echo "api_key: ${CROWDIN_API_TOKEN}" >> crowdin.yaml
|
||||
crowdin upload sources
|
67
.github/workflows/updatemessages.yml
vendored
Normal file
67
.github/workflows/updatemessages.yml
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
name: updatemessages
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
jobs:
|
||||
updatemessages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python 3.7
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Checkout submodules
|
||||
run: |
|
||||
git submodule init
|
||||
git submodule update
|
||||
- name: Install requirements
|
||||
run: |
|
||||
sudo apt-get install gettext
|
||||
curl -O https://artifacts.crowdin.com/repo/deb/crowdin.deb
|
||||
sudo dpkg -i crowdin.deb
|
||||
pip install -r requirements.txt
|
||||
pip install pymysql
|
||||
- name: Download strings from Crowdin
|
||||
env:
|
||||
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
|
||||
run: |
|
||||
cat > crowdin.yaml <<EOF
|
||||
project_identifier: dmoj
|
||||
|
||||
files:
|
||||
- source: /locale/en/LC_MESSAGES/django.po
|
||||
translation: /locale/%two_letters_code%/LC_MESSAGES/django.po
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh_Hans
|
||||
zh-TW: zh_Hant
|
||||
sr-CS: sr_Latn
|
||||
- source: /locale/en/LC_MESSAGES/djangojs.po
|
||||
translation: /locale/%two_letters_code%/LC_MESSAGES/djangojs.po
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh_Hans
|
||||
zh-TW: zh_Hant
|
||||
sr-CS: sr_Latn
|
||||
EOF
|
||||
echo "api_key: ${CROWDIN_API_TOKEN}" >> crowdin.yaml
|
||||
crowdin download
|
||||
rm crowdin.yaml
|
||||
- name: Cleanup
|
||||
run: |
|
||||
rm -rf src/
|
||||
git add locale
|
||||
git checkout .
|
||||
git clean -fd
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v1.4.1-multi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }}
|
||||
COMMIT_MESSAGE: 'i18n: update translations from Crowdin'
|
||||
PULL_REQUEST_TITLE: 'Update translations from Crowdin'
|
||||
PULL_REQUEST_BODY: This PR has been auto-generated to pull in latest translations from [Crowdin](https://translate.dmoj.ca).
|
||||
PULL_REQUEST_LABELS: i18n, enhancement
|
||||
PULL_REQUEST_REVIEWERS: Xyene, quantum5
|
||||
PULL_REQUEST_BRANCH: update-i18n
|
||||
BRANCH_SUFFIX: none
|
8
.gitmodules
vendored
Normal file
8
.gitmodules
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
[submodule "resources/pagedown"]
|
||||
path = resources/pagedown
|
||||
url = https://github.com/DMOJ/dmoj-pagedown.git
|
||||
branch = master
|
||||
[submodule "resources/libs"]
|
||||
path = resources/libs
|
||||
url = https://github.com/DMOJ/site-assets.git
|
||||
branch = master
|
|
@ -1,17 +0,0 @@
|
|||
repos:
|
||||
- repo: https://github.com/rtts/djhtml
|
||||
rev: 'v1.5.2' # replace with the latest tag on GitHub
|
||||
hooks:
|
||||
- id: djhtml
|
||||
entry: djhtml -i -t 2
|
||||
files: templates/.
|
||||
- id: djcss
|
||||
types: [scss]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/hadialqattan/pycln
|
||||
rev: 'v2.3.0'
|
||||
hooks:
|
||||
- id: pycln
|
2
502.html
2
502.html
|
@ -49,7 +49,7 @@
|
|||
<br>
|
||||
<div class="popup">
|
||||
<div>
|
||||
<img class="logo" src="logo.svg" alt="LQDOJ">
|
||||
<img class="logo" src="logo.png" alt="LQDOJ">
|
||||
</div>
|
||||
<h1 style="width: 100%;">Oops, LQDOJ is down now.</h1>
|
||||
</div>
|
||||
|
|
232
README.md
232
README.md
|
@ -17,12 +17,11 @@ Supported languages:
|
|||
- Assembly (x64)
|
||||
- AWK
|
||||
- C
|
||||
- C++03 / C++11 / C++14 / C++17 / C++20
|
||||
- C++03 / C++11 / C++14 / C++17
|
||||
- Java 11
|
||||
- Pascal
|
||||
- Perl
|
||||
- Python 2 / Python 3
|
||||
- PyPy 2 / PyPy 3
|
||||
|
||||
Support plagiarism detection via [Stanford MOSS](https://theory.stanford.edu/~aiken/moss/).
|
||||
|
||||
|
@ -31,196 +30,10 @@ Support plagiarism detection via [Stanford MOSS](https://theory.stanford.edu/~ai
|
|||
Most of the setup are the same as DMOJ installations. You can view the installation guide of DMOJ here: https://docs.dmoj.ca/#/site/installation.
|
||||
There is one minor change: Instead of `git clone https://github.com/DMOJ/site.git`, you clone this repo `git clone https://github.com/LQDJudge/online-judge.git`.
|
||||
|
||||
### Additional Steps in Production:
|
||||
|
||||
- Bước 1: cài các thư viện cần thiết
|
||||
- $ ở đây nghĩa là sudo. Ví dụ dòng đầu nghĩa là chạy lệnh `sudo apt update`
|
||||
|
||||
```jsx
|
||||
$ apt update
|
||||
$ apt install git gcc g++ make python3-dev python3-pip libxml2-dev libxslt1-dev zlib1g-dev gettext curl redis-server
|
||||
$ curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
$ apt install nodejs
|
||||
$ npm install -g sass postcss-cli postcss autoprefixer
|
||||
```
|
||||
|
||||
- Bước 2: tạo DB
|
||||
- Server đang dùng MariaDB ≥ 10.5, các bạn cũng có thể dùng Mysql nếu bị conflict
|
||||
- Nếu các bạn chạy lệnh dưới này xong mà version mariadb bị cũ (< 10.5) thì có thể tra google cách cài MariaDB mới nhất (10.5 hoặc 10.6).
|
||||
- Các bạn có thể thấy version MariaDB bằng cách gõ lệnh `sudo mysql` (Ctrl + C để quit)
|
||||
|
||||
```jsx
|
||||
$ apt update
|
||||
$ apt install mariadb-server libmysqlclient-dev
|
||||
```
|
||||
|
||||
- Bước 3: tạo table trong DB
|
||||
- Các bạn có thể thay tên table và password
|
||||
|
||||
```jsx
|
||||
$ sudo mysql
|
||||
mariadb> CREATE DATABASE dmoj DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;
|
||||
mariadb> GRANT ALL PRIVILEGES ON dmoj.* TO 'dmoj'@'localhost' IDENTIFIED BY '<password>';
|
||||
mariadb> exit
|
||||
```
|
||||
|
||||
- Bước 4: Cài đặt môi trường ảo (virtual env) và pull code
|
||||
- Nếu `pip3 install mysqlclient` bị lỗi thì thử chạy `pip3 install mysqlclient==2.1.1`
|
||||
|
||||
```jsx
|
||||
$ python3 -m venv dmojsite
|
||||
$ . dmojsite/bin/activate
|
||||
|
||||
(dmojsite) $ git clone https://github.com/LQDJudge/online-judge.git
|
||||
(dmojsite) $ cd online-judge
|
||||
(dmojsite) $ git submodule init
|
||||
(dmojsite) $ git submodule update
|
||||
(dmojsite) $ pip3 install -r requirements.txt
|
||||
(dmojsite) $ pip3 install mysqlclient
|
||||
(dmojsite) $ pre-commit install
|
||||
```
|
||||
|
||||
- Bước 5: tạo local_settings.py. Đây là file để custom setting cho Django. Các bạn tạo file vào `online-judge/dmoj/local_settings.py`
|
||||
- File mẫu: https://github.com/DMOJ/docs/blob/master/sample_files/local_settings.py
|
||||
- Nếu bạn đổi tên hoặc mật khẩu table databases thì thay đổi thông tin tương ứng trong `Databases`
|
||||
- Sau khi xong, chạy lệnh `(dmojsite) $ python3 manage.py check` để kiểm tra
|
||||
- Bước 6: Compile CSS và translation
|
||||
- Giải thích:
|
||||
- Lệnh 1 và 2 gọi sau mỗi lần thay đổi 1 file css hoặc file js (file html thì không cần)
|
||||
- Lệnh 3 và 4 gọi sau mỗi lần thay đổi file dịch
|
||||
- Note: Sau khi chạy lệnh này, folder tương ứng với STATIC_ROOT trong local_settings phải được tạo. Nếu chưa được tạo thì mình cần tạo folder đó trước khi chạy 2 lệnh đầu.
|
||||
|
||||
```jsx
|
||||
(dmojsite) $ ./make_style.sh
|
||||
(dmojsite) $ python3 manage.py collectstatic
|
||||
(dmojsite) $ python3 manage.py compilemessages
|
||||
(dmojsite) $ python3 manage.py compilejsi18n
|
||||
```
|
||||
|
||||
- Bước 7: Thêm dữ liệu vào DB
|
||||
|
||||
```jsx
|
||||
(dmojsite) $ python3 manage.py migrate
|
||||
(dmojsite) $ python3 manage.py loaddata navbar
|
||||
(dmojsite) $ python3 manage.py loaddata language_small
|
||||
(dmojsite) $ python3 manage.py loaddata demo
|
||||
```
|
||||
|
||||
- Bước 8: Chạy site. Đến đây thì cơ bản đã hoàn thành (chưa có judge, websocket, celery). Các bạn có thể truy cập tại `localhost:8000`
|
||||
|
||||
```jsx
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
**Một số lưu ý:**
|
||||
|
||||
1. (WSL) có thể tải ứng dụng Terminal trong Windows Store
|
||||
2. (WSL) mỗi lần mở ubuntu, các bạn cần chạy lệnh sau để mariadb khởi động: `sudo service mysql restart` (tương tự cho một số service khác như memcached, celery)
|
||||
3. Sau khi cài đặt, các bạn chỉ cần activate virtual env và chạy lệnh runserver là ok
|
||||
```jsx
|
||||
. dmojsite/bin/activate
|
||||
python3 manage.py runserver
|
||||
```
|
||||
5. Đối với nginx, sau khi config xong theo guide của DMOJ, bạn cần thêm location như sau để sử dụng được tính năng profile image, thay thế `path/to/oj` thành đường dẫn nơi bạn đã clone source code.
|
||||
|
||||
```
|
||||
location /profile_images/ {
|
||||
root /path/to/oj;
|
||||
}
|
||||
```
|
||||
|
||||
6. Quy trình dev:
|
||||
1. Sau khi thay đổi code thì django tự build lại, các bạn chỉ cần F5
|
||||
2. Một số style nằm trong các file .scss. Các bạn cần recompile css thì mới thấy được thay đổi.
|
||||
|
||||
**Optional:**
|
||||
|
||||
************Alias:************ Các bạn có thể lưu các alias này để sau này dùng cho nhanh
|
||||
|
||||
- mtrans: để generate translation khi các bạn add một string trong code
|
||||
- trans: compile translation (sau khi bạn đã dịch tiếng Việt)
|
||||
- cr: chuyển tới folder OJ
|
||||
- pr: chạy server
|
||||
- sm: restart service (chủ yếu dùng cho WSL)
|
||||
- sd: activate virtual env
|
||||
- css: compile các file css
|
||||
|
||||
```jsx
|
||||
alias mtrans='python3 manage.py makemessages -l vi && python3 manage.py makedmojmessages -l vi'
|
||||
alias pr='python3 manage.py runserver'
|
||||
alias sd='source ~/LQDOJ/dmojsite/bin/activate'
|
||||
alias sm='sudo service mysql restart && sudo service redis-server start && sudo service memcached start'
|
||||
alias trans='python3 manage.py compilemessages -l vi && python3 manage.py compilejsi18n -l vi'
|
||||
alias cr='cd ~/LQDOJ/online-judge'
|
||||
alias css='./make_style.sh && python3 manage.py collectstatic --noinput'
|
||||
```
|
||||
|
||||
**Memcached:** dùng cho in-memory cache
|
||||
|
||||
```jsx
|
||||
$ sudo apt install memcached
|
||||
```
|
||||
|
||||
**Websocket:** dùng để live update (như chat)
|
||||
|
||||
- Tạo file online-judge/websocket/config.js
|
||||
|
||||
```jsx
|
||||
module.exports = {
|
||||
get_host: '127.0.0.1',
|
||||
get_port: 15100,
|
||||
post_host: '127.0.0.1',
|
||||
post_port: 15101,
|
||||
http_host: '127.0.0.1',
|
||||
http_port: 15102,
|
||||
long_poll_timeout: 29000,
|
||||
};
|
||||
```
|
||||
|
||||
- Cài các thư viện
|
||||
|
||||
```jsx
|
||||
(dmojsite) $ npm install qu ws simplesets
|
||||
(dmojsite) $ pip3 install websocket-client
|
||||
```
|
||||
|
||||
- Khởi động (trong 1 tab riêng)
|
||||
|
||||
```jsx
|
||||
(dmojsite) $ node websocket/daemon.js
|
||||
```
|
||||
|
||||
**************Celery:************** (dùng cho một số task như batch rejudge_
|
||||
|
||||
```jsx
|
||||
celery -A dmoj_celery worker
|
||||
```
|
||||
|
||||
**************Judge:**************
|
||||
|
||||
- Cài đặt ở 1 folder riêng bên ngoài site:
|
||||
|
||||
```jsx
|
||||
$ apt install python3-dev python3-pip build-essential libseccomp-dev
|
||||
$ git clone https://github.com/LQDJudge/judge-server.git
|
||||
$ cd judge-server
|
||||
$ sudo pip3 install -e .
|
||||
```
|
||||
|
||||
- Tạo một file judge.yml ở bên ngoài folder judge-server (file mẫu https://github.com/DMOJ/docs/blob/master/sample_files/judge_conf.yml)
|
||||
- Thêm judge vào site bằng UI: Admin → Judge → Thêm Judge → nhập id và key (chỉ cần thêm 1 lần) hoặc dùng lệnh `(dmojsite) $ python3 managed.py addjudge <id> <key>`.
|
||||
- Chạy Bridge (cầu nối giữa judge và site) trong 1 tab riêng trong folder online-judge:
|
||||
|
||||
```jsx
|
||||
(dmojsite) $ python3 managed.py runbridged
|
||||
```
|
||||
|
||||
- Khởi động Judge (trong 1 tab riêng):
|
||||
|
||||
```jsx
|
||||
$ dmoj -c judge.yml localhost
|
||||
```
|
||||
|
||||
- Lưu ý: mỗi lần sau này muốn chạy judge thì mở 1 tab cho bridge và n tab cho judge. Mỗi judge cần 1 file yml khác nhau (chứa authentication khác nhau)
|
||||
1. To use newsletter (email sending), go to admin and create a newsletter.
|
||||
2. Change the domain name and website name in Admin page: Navigation Bars/Sites
|
||||
|
||||
### Some frequent difficulties when installation:
|
||||
|
||||
|
@ -228,21 +41,6 @@ $ dmoj -c judge.yml localhost
|
|||
2. Missing the problem folder in `local_settings.py`. You need to create a folder to contain all problem packages and configure in `local_settings.py`.
|
||||
3. Missing static folder in `local_settings.py`. Similar to problem folder, make sure to configure `STATIC_FILES` inside `local_settings.py`.
|
||||
4. Missing configure file for judges. Each judge must have a seperate configure file. To create this file, you can run `python dmojauto-conf`. Checkout all sample files here https://github.com/DMOJ/docs/blob/master/sample_files.
|
||||
5. Missing timezone data for SQL. If you're using Ubuntu and you're following DMOJ's installation guide for the server, and you are getting the error mentioned in https://github.com/LQDJudge/online-judge/issues/45, then you can follow this method to fix:
|
||||
```
|
||||
mysql
|
||||
-- You may have to do this if you haven't set root password for MySQL, replace mypass with your password
|
||||
-- SET PASSWORD FOR 'root'@'localhost' = PASSWORD('mypass');
|
||||
-- FLUSH PRIVILEGES;
|
||||
exit
|
||||
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p
|
||||
mysql -u root -p -e "flush tables;" mysql
|
||||
```
|
||||
6. Missing the chat secret key, you must generate a Fernet key, and assign a variable in `local_settings.py` like this
|
||||
```python
|
||||
CHAT_SECRET_KEY = "81HqDtbqAywKSOumSxxxxxxxxxxxxxxxxx="
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -255,12 +53,12 @@ source dmojsite/bin/activate
|
|||
|
||||
2. Run server:
|
||||
```bash
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
3. Create a bridge (this is opened in a different terminal with the second step if you are using the same machine)
|
||||
```bash
|
||||
python3 manage.py runbridged
|
||||
python manage.py runbridged
|
||||
```
|
||||
|
||||
4. Create a judge (another terminal)
|
||||
|
@ -281,22 +79,20 @@ celery -A dmoj_celery worker
|
|||
node websocket/daemon.js
|
||||
```
|
||||
|
||||
7. To use subdomain for each organization, go to admin page -> navigation bar -> sites, add domain name (e.g, "localhost:8000"). Then go to add `USE_SUBDOMAIN = True` to local_settings.py.
|
||||
|
||||
## Deploy
|
||||
Most of the steps are similar to Django tutorials. Here are two usual steps:
|
||||
|
||||
1. Update vietnamese translation:
|
||||
- If you add any new phrases in the code, ```python3 manage.py makemessages```
|
||||
- If you add any new phrases in the code, ```python manage.py makemessages```
|
||||
- go to `locale/vi`
|
||||
- modify `.po` file
|
||||
- ```python3 manage.py compilemessages```
|
||||
- ```python3 manage.py compilejsi18n```
|
||||
- ```python manage.py compilemessages```
|
||||
- ```python manage.py compilejsi18n```
|
||||
|
||||
2. Update styles (using SASS)
|
||||
- Change .css/.scss files in `resources` folder
|
||||
- ```./make_style.sh && python3 manage.py collectstatic```
|
||||
- Sometimes you need to press `Ctrl + F5` to see the new user interface in browser.
|
||||
- ```./make_style && python manage.py collectstatic```
|
||||
- Sometimes you need to `Ctrl + F5` to see the new user interface in browser.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
@ -304,8 +100,7 @@ Most of the steps are similar to Django tutorials. Here are two usual steps:
|
|||
|
||||
Leaderboard with information about contest rating, performance points and real name of all users.
|
||||
|
||||
![](https://raw.githubusercontent.com/emladevops/LQDOJ-image/main/brave_SK67WA26FA.png#gh-light-mode-only)
|
||||
![](https://raw.githubusercontent.com/emladevops/LQDOJ-image/main/brave_cmqqCnwaFc.png#gh-dark-mode-only)
|
||||
![](https://i.imgur.com/ampxHXM.png)
|
||||
|
||||
### Admin dashboard
|
||||
|
||||
|
@ -323,5 +118,4 @@ You can write the problems' statement in Markdown with LaTeX figures and formula
|
|||
|
||||
Users can communicate with each other and can see who's online.
|
||||
|
||||
![](https://raw.githubusercontent.com/emladevops/LQDOJ-image/main/brave_kPsC5bJluc.png#gh-light-mode-only)
|
||||
![](https://raw.githubusercontent.com/emladevops/LQDOJ-image/main/brave_AtrEzXzEAx.png#gh-dark-mode-only)
|
||||
![](https://i.imgur.com/y9SGCgl.png)
|
||||
|
|
|
@ -3,6 +3,3 @@ from django.apps import AppConfig
|
|||
|
||||
class ChatBoxConfig(AppConfig):
|
||||
name = "chat_box"
|
||||
|
||||
def ready(self):
|
||||
from . import models
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-06-18 07:52
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0008_ignore"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="ignore",
|
||||
name="user",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="ignored_chat_users",
|
||||
to="judge.Profile",
|
||||
verbose_name="user",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-10-27 20:00
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0135_auto_20221028_0300"),
|
||||
("chat_box", "0009_auto_20220618_1452"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name="userroom",
|
||||
unique_together={("user", "room")},
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# Generated by Django 3.2.18 on 2023-02-18 21:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0010_auto_20221028_0300"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="message",
|
||||
name="hidden",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=False, verbose_name="is hidden"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,34 +0,0 @@
|
|||
# Generated by Django 3.2.18 on 2023-03-08 07:17
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0154_add_submission_indexes"),
|
||||
("chat_box", "0011_alter_message_hidden"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="message",
|
||||
options={
|
||||
"ordering": ("-id",),
|
||||
"verbose_name": "message",
|
||||
"verbose_name_plural": "messages",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="message",
|
||||
name="hidden",
|
||||
field=models.BooleanField(default=False, verbose_name="is hidden"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="message",
|
||||
index=models.Index(
|
||||
fields=["hidden", "room", "-id"], name="chat_box_me_hidden_b2307a_idx"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# Generated by Django 3.2.18 on 2023-08-28 01:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0012_auto_20230308_1417"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="message",
|
||||
name="time",
|
||||
field=models.DateTimeField(
|
||||
auto_now_add=True, db_index=True, verbose_name="posted time"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,38 +0,0 @@
|
|||
# Generated by Django 3.2.18 on 2023-08-28 06:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate(apps, schema_editor):
|
||||
UserRoom = apps.get_model("chat_box", "UserRoom")
|
||||
Message = apps.get_model("chat_box", "Message")
|
||||
|
||||
for ur in UserRoom.objects.all():
|
||||
if not ur.room:
|
||||
continue
|
||||
messages = ur.room.message_set
|
||||
last_msg = messages.first()
|
||||
try:
|
||||
if last_msg and last_msg.author != ur.user:
|
||||
ur.unread_count = messages.filter(time__gte=ur.last_seen).count()
|
||||
else:
|
||||
ur.unread_count = 0
|
||||
ur.save()
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0013_alter_message_time"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="userroom",
|
||||
name="unread_count",
|
||||
field=models.IntegerField(db_index=True, default=0),
|
||||
),
|
||||
migrations.RunPython(migrate, migrations.RunPython.noop, atomic=True),
|
||||
]
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 3.2.18 on 2023-11-02 01:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate(apps, schema_editor):
|
||||
Room = apps.get_model("chat_box", "Room")
|
||||
Message = apps.get_model("chat_box", "Message")
|
||||
|
||||
for room in Room.objects.all():
|
||||
messages = room.message_set
|
||||
last_msg = messages.first()
|
||||
if last_msg:
|
||||
room.last_msg_time = last_msg.time
|
||||
room.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0014_userroom_unread_count"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="room",
|
||||
name="last_msg_time",
|
||||
field=models.DateTimeField(
|
||||
db_index=True, null=True, verbose_name="last seen"
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate, migrations.RunPython.noop, atomic=True),
|
||||
]
|
|
@ -1,32 +0,0 @@
|
|||
# Generated by Django 3.2.18 on 2024-08-22 03:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def remove_duplicates(apps, schema_editor):
|
||||
Room = apps.get_model("chat_box", "Room")
|
||||
seen = set()
|
||||
|
||||
for room in Room.objects.all():
|
||||
pair = (room.user_one_id, room.user_two_id)
|
||||
reverse_pair = (room.user_two_id, room.user_one_id)
|
||||
|
||||
if pair in seen or reverse_pair in seen:
|
||||
room.delete()
|
||||
else:
|
||||
seen.add(pair)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0015_room_last_msg_time"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_duplicates),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="room",
|
||||
unique_together={("user_one", "user_two")},
|
||||
),
|
||||
]
|
|
@ -1,14 +1,12 @@
|
|||
from django.db import models
|
||||
from django.db.models import CASCADE, Q
|
||||
from django.db.models import CASCADE
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
from judge.models.profile import Profile
|
||||
from judge.caching import cache_wrapper
|
||||
|
||||
|
||||
__all__ = ["Message", "Room", "UserRoom", "Ignore"]
|
||||
__all__ = ["Message"]
|
||||
|
||||
|
||||
class Room(models.Model):
|
||||
|
@ -18,44 +16,20 @@ class Room(models.Model):
|
|||
user_two = models.ForeignKey(
|
||||
Profile, related_name="user_two", verbose_name="user 2", on_delete=CASCADE
|
||||
)
|
||||
last_msg_time = models.DateTimeField(
|
||||
verbose_name=_("last seen"), null=True, db_index=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = "chat_box"
|
||||
unique_together = ("user_one", "user_two")
|
||||
|
||||
@cached_property
|
||||
def _cached_info(self):
|
||||
return get_room_info(self.id)
|
||||
|
||||
def contain(self, profile):
|
||||
return profile.id in [self.user_one_id, self.user_two_id]
|
||||
return self.user_one == profile or self.user_two == profile
|
||||
|
||||
def other_user(self, profile):
|
||||
return self.user_one if profile == self.user_two else self.user_two
|
||||
|
||||
def other_user_id(self, profile):
|
||||
user_ids = [self.user_one_id, self.user_two_id]
|
||||
return sum(user_ids) - profile.id
|
||||
|
||||
def users(self):
|
||||
return [self.user_one, self.user_two]
|
||||
|
||||
def last_message_body(self):
|
||||
return self._cached_info["last_message"]
|
||||
|
||||
@classmethod
|
||||
def prefetch_room_cache(self, room_ids):
|
||||
get_room_info.prefetch_multi([(i,) for i in room_ids])
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE)
|
||||
time = models.DateTimeField(
|
||||
verbose_name=_("posted time"), auto_now_add=True, db_index=True
|
||||
)
|
||||
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
||||
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
|
||||
hidden = models.BooleanField(verbose_name="is hidden", default=False)
|
||||
room = models.ForeignKey(
|
||||
|
@ -63,17 +37,15 @@ class Message(models.Model):
|
|||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
new_message = self.id
|
||||
self.body = self.body.strip()
|
||||
super(Message, self).save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
app_label = "chat_box"
|
||||
verbose_name = "message"
|
||||
verbose_name_plural = "messages"
|
||||
ordering = ("-id",)
|
||||
indexes = [
|
||||
models.Index(fields=["hidden", "room", "-id"]),
|
||||
]
|
||||
app_label = "chat_box"
|
||||
ordering = ("-time",)
|
||||
|
||||
|
||||
class UserRoom(models.Model):
|
||||
|
@ -82,52 +54,35 @@ class UserRoom(models.Model):
|
|||
Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True
|
||||
)
|
||||
last_seen = models.DateTimeField(verbose_name=_("last seen"), auto_now_add=True)
|
||||
unread_count = models.IntegerField(default=0, db_index=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("user", "room")
|
||||
app_label = "chat_box"
|
||||
|
||||
|
||||
class Ignore(models.Model):
|
||||
user = models.OneToOneField(
|
||||
user = models.ForeignKey(
|
||||
Profile,
|
||||
related_name="ignored_chat_users",
|
||||
verbose_name=_("user"),
|
||||
on_delete=CASCADE,
|
||||
db_index=True,
|
||||
)
|
||||
ignored_users = models.ManyToManyField(Profile)
|
||||
|
||||
class Meta:
|
||||
app_label = "chat_box"
|
||||
|
||||
@classmethod
|
||||
def is_ignored(self, current_user, new_friend):
|
||||
try:
|
||||
return current_user.ignored_chat_users.ignored_users.filter(
|
||||
id=new_friend.id
|
||||
).exists()
|
||||
return (
|
||||
current_user.ignored_chat_users.get()
|
||||
.ignored_users.filter(id=new_friend.id)
|
||||
.exists()
|
||||
)
|
||||
except:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_ignored_users(self, user):
|
||||
try:
|
||||
return self.objects.get(user=user).ignored_users.all()
|
||||
return self.objects.filter(user=user)[0].ignored_users.all()
|
||||
except:
|
||||
return Profile.objects.none()
|
||||
|
||||
@classmethod
|
||||
def get_ignored_rooms(self, user):
|
||||
try:
|
||||
ignored_users = self.objects.get(user=user).ignored_users.all()
|
||||
return Room.objects.filter(Q(user_one=user) | Q(user_two=user)).filter(
|
||||
Q(user_one__in=ignored_users) | Q(user_two__in=ignored_users)
|
||||
)
|
||||
except:
|
||||
return Room.objects.none()
|
||||
|
||||
@classmethod
|
||||
def add_ignore(self, current_user, friend):
|
||||
ignore, created = self.objects.get_or_create(user=current_user)
|
||||
|
@ -144,11 +99,3 @@ class Ignore(models.Model):
|
|||
self.remove_ignore(current_user, friend)
|
||||
else:
|
||||
self.add_ignore(current_user, friend)
|
||||
|
||||
|
||||
@cache_wrapper(prefix="Rinfo")
|
||||
def get_room_info(room_id):
|
||||
last_msg = Message.objects.filter(room_id=room_id).first()
|
||||
return {
|
||||
"last_message": last_msg.body if last_msg else None,
|
||||
}
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
from cryptography.fernet import Fernet
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import OuterRef, Count, Subquery, IntegerField, Q
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
from chat_box.models import Ignore, Message, UserRoom, Room
|
||||
|
||||
from judge.caching import cache_wrapper
|
||||
|
||||
secret_key = settings.CHAT_SECRET_KEY
|
||||
fernet = Fernet(secret_key)
|
||||
|
@ -26,26 +18,3 @@ def decrypt_url(message_encrypted):
|
|||
return int(creator_id), int(other_id)
|
||||
except Exception as e:
|
||||
return None, None
|
||||
|
||||
|
||||
def encrypt_channel(channel):
|
||||
return (
|
||||
hmac.new(
|
||||
settings.CHAT_SECRET_KEY.encode(),
|
||||
channel.encode(),
|
||||
hashlib.sha512,
|
||||
).hexdigest()[:16]
|
||||
+ "%s" % channel
|
||||
)
|
||||
|
||||
|
||||
@cache_wrapper(prefix="gub")
|
||||
def get_unread_boxes(profile):
|
||||
ignored_rooms = Ignore.get_ignored_rooms(profile)
|
||||
unread_boxes = (
|
||||
UserRoom.objects.filter(user=profile, unread_count__gt=0)
|
||||
.exclude(room__in=ignored_rooms)
|
||||
.count()
|
||||
)
|
||||
|
||||
return unread_boxes
|
||||
|
|
|
@ -21,57 +21,43 @@ from django.db.models import (
|
|||
Exists,
|
||||
Count,
|
||||
IntegerField,
|
||||
F,
|
||||
Max,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import reverse
|
||||
|
||||
import datetime
|
||||
|
||||
from judge import event_poster as event
|
||||
from judge.jinja2.gravatar import gravatar
|
||||
from judge.models import Friend
|
||||
|
||||
from chat_box.models import Message, Profile, Room, UserRoom, Ignore, get_room_info
|
||||
from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel, get_unread_boxes
|
||||
from chat_box.models import Message, Profile, Room, UserRoom, Ignore
|
||||
from chat_box.utils import encrypt_url, decrypt_url
|
||||
|
||||
from reversion import revisions
|
||||
import json
|
||||
|
||||
|
||||
class ChatView(ListView):
|
||||
context_object_name = "message"
|
||||
template_name = "chat/chat.html"
|
||||
title = _("LQDOJ Chat")
|
||||
title = _("Chat Box")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.room_id = None
|
||||
self.room = None
|
||||
self.paginate_by = 50
|
||||
self.messages = None
|
||||
self.first_page_size = 20 # only for first request
|
||||
self.follow_up_page_size = 50
|
||||
self.paginator = None
|
||||
|
||||
def get_queryset(self):
|
||||
return self.messages
|
||||
|
||||
def has_next(self):
|
||||
try:
|
||||
msg = Message.objects.filter(room=self.room_id).earliest("id")
|
||||
except Exception as e:
|
||||
return False
|
||||
return msg not in self.messages
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
request_room = kwargs["room_id"]
|
||||
page_size = self.follow_up_page_size
|
||||
try:
|
||||
last_id = int(request.GET.get("last_id"))
|
||||
except Exception:
|
||||
last_id = 1e15
|
||||
page_size = self.first_page_size
|
||||
only_messages = request.GET.get("only_messages")
|
||||
page = request.GET.get("page")
|
||||
|
||||
if request_room:
|
||||
try:
|
||||
|
@ -83,21 +69,23 @@ class ChatView(ListView):
|
|||
else:
|
||||
request_room = None
|
||||
|
||||
self.room_id = request_room
|
||||
self.messages = (
|
||||
Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id)
|
||||
.select_related("author")
|
||||
.only("body", "time", "author__rating", "author__display_rank")[:page_size]
|
||||
)
|
||||
if not only_messages:
|
||||
if request_room != self.room_id or not self.messages:
|
||||
self.room_id = request_room
|
||||
self.messages = Message.objects.filter(hidden=False, room=self.room_id)
|
||||
self.paginator = Paginator(self.messages, self.paginate_by)
|
||||
|
||||
if page == None:
|
||||
update_last_seen(request, **kwargs)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
cur_page = self.paginator.get_page(page)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"chat/message_list.html",
|
||||
{
|
||||
"object_list": self.messages,
|
||||
"has_next": self.has_next(),
|
||||
"object_list": cur_page.object_list,
|
||||
"num_pages": self.paginator.num_pages,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -106,14 +94,9 @@ class ChatView(ListView):
|
|||
|
||||
context["title"] = self.title
|
||||
context["last_msg"] = event.last()
|
||||
context["status_sections"] = get_status_context(self.request.profile)
|
||||
context["status_sections"] = get_status_context(self.request)
|
||||
context["room"] = self.room_id
|
||||
context["has_next"] = self.has_next()
|
||||
context["unread_count_lobby"] = get_unread_count(None, self.request.profile)
|
||||
context["chat_channel"] = encrypt_channel(
|
||||
"chat_" + str(self.request.profile.id)
|
||||
)
|
||||
context["chat_lobby_channel"] = encrypt_channel("chat_lobby")
|
||||
if self.room:
|
||||
users_room = [self.room.user_one, self.room.user_two]
|
||||
users_room.remove(self.request.profile)
|
||||
|
@ -137,89 +120,36 @@ def delete_message(request):
|
|||
ret = {"delete": "done"}
|
||||
|
||||
if request.method == "GET":
|
||||
return HttpResponseBadRequest()
|
||||
return JsonResponse(ret)
|
||||
|
||||
try:
|
||||
messid = int(request.POST.get("message"))
|
||||
mess = Message.objects.get(id=messid)
|
||||
except:
|
||||
return HttpResponseBadRequest()
|
||||
if request.user.is_staff:
|
||||
try:
|
||||
messid = int(request.POST.get("message"))
|
||||
mess = Message.objects.get(id=messid)
|
||||
except:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if not request.user.is_staff and request.profile != mess.author:
|
||||
return HttpResponseBadRequest()
|
||||
mess.hidden = True
|
||||
mess.save()
|
||||
|
||||
mess.hidden = True
|
||||
mess.save()
|
||||
return JsonResponse(ret)
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
|
||||
def mute_message(request):
|
||||
ret = {"mute": "done"}
|
||||
|
||||
if request.method == "GET":
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if not request.user.is_staff:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
messid = int(request.POST.get("message"))
|
||||
mess = Message.objects.get(id=messid)
|
||||
except:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
with revisions.create_revision():
|
||||
revisions.set_comment(_("Mute chat") + ": " + mess.body)
|
||||
revisions.set_user(request.user)
|
||||
mess.author.mute = True
|
||||
mess.author.save()
|
||||
|
||||
Message.objects.filter(room=None, author=mess.author).update(hidden=True)
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
|
||||
def check_valid_message(request, room):
|
||||
if not room and len(request.POST["body"]) > 200:
|
||||
return False
|
||||
|
||||
if not can_access_room(request, room) or request.profile.mute:
|
||||
return False
|
||||
|
||||
last_msg = Message.objects.filter(room=room).first()
|
||||
if (
|
||||
last_msg
|
||||
and last_msg.author == request.profile
|
||||
and last_msg.body == request.POST["body"].strip()
|
||||
):
|
||||
return False
|
||||
|
||||
if not room:
|
||||
four_last_msg = Message.objects.filter(room=room).order_by("-id")[:4]
|
||||
if len(four_last_msg) >= 4:
|
||||
same_author = all(msg.author == request.profile for msg in four_last_msg)
|
||||
time_diff = timezone.now() - four_last_msg[3].time
|
||||
if same_author and time_diff.total_seconds() < 300:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@login_required
|
||||
def post_message(request):
|
||||
ret = {"msg": "posted"}
|
||||
|
||||
if request.method != "POST":
|
||||
return HttpResponseBadRequest()
|
||||
if len(request.POST["body"]) > 5000 or len(request.POST["body"].strip()) == 0:
|
||||
if len(request.POST["body"]) > 5000:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
room = None
|
||||
if request.POST["room"]:
|
||||
room = Room.objects.get(id=request.POST["room"])
|
||||
|
||||
if not check_valid_message(request, room):
|
||||
if not can_access_room(request, room) or request.profile.mute:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
new_message = Message(author=request.profile, body=request.POST["body"], room=room)
|
||||
|
@ -227,7 +157,7 @@ def post_message(request):
|
|||
|
||||
if not room:
|
||||
event.post(
|
||||
encrypt_channel("chat_lobby"),
|
||||
"chat_lobby",
|
||||
{
|
||||
"type": "lobby",
|
||||
"author_id": request.profile.id,
|
||||
|
@ -237,13 +167,9 @@ def post_message(request):
|
|||
},
|
||||
)
|
||||
else:
|
||||
get_room_info.dirty(room.id)
|
||||
room.last_msg_time = new_message.time
|
||||
room.save()
|
||||
|
||||
for user in room.users():
|
||||
event.post(
|
||||
encrypt_channel("chat_" + str(user.id)),
|
||||
"chat_" + str(user.id),
|
||||
{
|
||||
"type": "private",
|
||||
"author_id": request.profile.id,
|
||||
|
@ -252,17 +178,14 @@ def post_message(request):
|
|||
"tmp_id": request.POST.get("tmp_id"),
|
||||
},
|
||||
)
|
||||
if user != request.profile:
|
||||
UserRoom.objects.filter(user=user, room=room).update(
|
||||
unread_count=F("unread_count") + 1
|
||||
)
|
||||
get_unread_boxes.dirty(user)
|
||||
|
||||
return JsonResponse(ret)
|
||||
|
||||
|
||||
def can_access_room(request, room):
|
||||
return not room or room.contain(request.profile)
|
||||
return (
|
||||
not room or room.user_one == request.profile or room.user_two == request.profile
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -278,7 +201,7 @@ def chat_message_ajax(request):
|
|||
try:
|
||||
message = Message.objects.filter(hidden=False).get(id=message_id)
|
||||
room = message.room
|
||||
if not can_access_room(request, room):
|
||||
if room and not room.contain(request.profile):
|
||||
return HttpResponse("Unauthorized", status=401)
|
||||
except Message.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
@ -301,35 +224,35 @@ def update_last_seen(request, **kwargs):
|
|||
room_id = request.POST.get("room")
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
profile = request.profile
|
||||
room = None
|
||||
if room_id:
|
||||
room = Room.objects.filter(id=int(room_id)).first()
|
||||
room = Room.objects.get(id=int(room_id))
|
||||
except Room.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
except Exception as e:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if not can_access_room(request, room):
|
||||
if room and not room.contain(profile):
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
user_room, _ = UserRoom.objects.get_or_create(user=profile, room=room)
|
||||
user_room.last_seen = timezone.now()
|
||||
user_room.unread_count = 0
|
||||
user_room.save()
|
||||
|
||||
get_unread_boxes.dirty(profile)
|
||||
|
||||
return JsonResponse({"msg": "updated"})
|
||||
|
||||
|
||||
def get_online_count():
|
||||
last_5_minutes = timezone.now() - timezone.timedelta(minutes=5)
|
||||
return Profile.objects.filter(last_access__gte=last_5_minutes).count()
|
||||
last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
|
||||
return Profile.objects.filter(last_access__gte=last_two_minutes).count()
|
||||
|
||||
|
||||
def get_user_online_status(user):
|
||||
time_diff = timezone.now() - user.last_access
|
||||
is_online = time_diff <= timezone.timedelta(minutes=5)
|
||||
is_online = time_diff <= timezone.timedelta(minutes=2)
|
||||
return is_online
|
||||
|
||||
|
||||
|
@ -366,66 +289,47 @@ def user_online_status_ajax(request):
|
|||
)
|
||||
|
||||
|
||||
def get_online_status(profile, other_profile_ids, rooms=None):
|
||||
if not other_profile_ids:
|
||||
def get_online_status(request_user, queryset, rooms=None):
|
||||
if not queryset:
|
||||
return None
|
||||
Profile.prefetch_profile_cache(other_profile_ids)
|
||||
|
||||
joined_ids = ",".join([str(id) for id in other_profile_ids])
|
||||
other_profiles = Profile.objects.raw(
|
||||
f"SELECT * from judge_profile where id in ({joined_ids}) order by field(id,{joined_ids})"
|
||||
)
|
||||
last_5_minutes = timezone.now() - timezone.timedelta(minutes=5)
|
||||
last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
|
||||
ret = []
|
||||
if rooms:
|
||||
unread_count = get_unread_count(rooms, profile)
|
||||
count = {}
|
||||
last_msg = {}
|
||||
room_of_user = {}
|
||||
for i in unread_count:
|
||||
room = Room.objects.get(id=i["room"])
|
||||
other_profile = room.other_user(profile)
|
||||
count[other_profile.id] = i["unread_count"]
|
||||
rooms = Room.objects.filter(id__in=rooms)
|
||||
for room in rooms:
|
||||
other_profile_id = room.other_user_id(profile)
|
||||
last_msg[other_profile_id] = room.last_message_body()
|
||||
room_of_user[other_profile_id] = room.id
|
||||
|
||||
for other_profile in other_profiles:
|
||||
if rooms:
|
||||
unread_count = get_unread_count(rooms, request_user)
|
||||
count = {}
|
||||
for i in unread_count:
|
||||
count[i["other_user"]] = i["unread_count"]
|
||||
|
||||
for user in queryset:
|
||||
is_online = False
|
||||
if other_profile.last_access >= last_5_minutes:
|
||||
if user.last_access >= last_two_minutes:
|
||||
is_online = True
|
||||
user_dict = {"user": other_profile, "is_online": is_online}
|
||||
if rooms:
|
||||
user_dict.update(
|
||||
{
|
||||
"unread_count": count.get(other_profile.id),
|
||||
"last_msg": last_msg.get(other_profile.id),
|
||||
"room": room_of_user.get(other_profile.id),
|
||||
}
|
||||
)
|
||||
user_dict["url"] = encrypt_url(profile.id, other_profile.id)
|
||||
user_dict = {"user": user, "is_online": is_online}
|
||||
if rooms and user.id in count:
|
||||
user_dict["unread_count"] = count[user.id]
|
||||
user_dict["url"] = encrypt_url(request_user.id, user.id)
|
||||
ret.append(user_dict)
|
||||
return ret
|
||||
|
||||
|
||||
def get_status_context(profile, include_ignored=False):
|
||||
def get_status_context(request, include_ignored=False):
|
||||
if include_ignored:
|
||||
ignored_users = []
|
||||
ignored_users = Profile.objects.none()
|
||||
queryset = Profile.objects
|
||||
else:
|
||||
ignored_users = list(
|
||||
Ignore.get_ignored_users(profile).values_list("id", flat=True)
|
||||
)
|
||||
ignored_users = Ignore.get_ignored_users(request.profile)
|
||||
queryset = Profile.objects.exclude(id__in=ignored_users)
|
||||
|
||||
last_5_minutes = timezone.now() - timezone.timedelta(minutes=5)
|
||||
last_two_minutes = timezone.now() - timezone.timedelta(minutes=2)
|
||||
recent_profile = (
|
||||
Room.objects.filter(Q(user_one=profile) | Q(user_two=profile))
|
||||
Room.objects.filter(Q(user_one=request.profile) | Q(user_two=request.profile))
|
||||
.annotate(
|
||||
last_msg_time=Subquery(
|
||||
Message.objects.filter(room=OuterRef("pk")).values("time")[:1]
|
||||
),
|
||||
other_user=Case(
|
||||
When(user_one=profile, then="user_two"),
|
||||
When(user_one=request.profile, then="user_two"),
|
||||
default="user_one",
|
||||
),
|
||||
)
|
||||
|
@ -435,24 +339,50 @@ def get_status_context(profile, include_ignored=False):
|
|||
.values("other_user", "id")[:20]
|
||||
)
|
||||
|
||||
recent_profile_ids = [str(i["other_user"]) for i in recent_profile]
|
||||
recent_profile_id = [str(i["other_user"]) for i in recent_profile]
|
||||
joined_id = ",".join(recent_profile_id)
|
||||
recent_rooms = [int(i["id"]) for i in recent_profile]
|
||||
Room.prefetch_room_cache(recent_rooms)
|
||||
|
||||
recent_list = None
|
||||
if joined_id:
|
||||
recent_list = Profile.objects.raw(
|
||||
f"SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})"
|
||||
)
|
||||
friend_list = (
|
||||
Friend.get_friend_profiles(request.profile)
|
||||
.exclude(id__in=recent_profile_id)
|
||||
.exclude(id__in=ignored_users)
|
||||
.order_by("-last_access")
|
||||
)
|
||||
admin_list = (
|
||||
queryset.filter(display_rank="admin")
|
||||
.exclude(id__in=recent_profile_ids)
|
||||
.values_list("id", flat=True)
|
||||
.exclude(id__in=friend_list)
|
||||
.exclude(id__in=recent_profile_id)
|
||||
)
|
||||
all_user_status = (
|
||||
queryset.filter(display_rank="user", last_access__gte=last_two_minutes)
|
||||
.annotate(is_online=Case(default=True, output_field=BooleanField()))
|
||||
.order_by("-rating")
|
||||
.exclude(id__in=friend_list)
|
||||
.exclude(id__in=admin_list)
|
||||
.exclude(id__in=recent_profile_id)[:30]
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"title": _("Recent"),
|
||||
"user_list": get_online_status(profile, recent_profile_ids, recent_rooms),
|
||||
"title": "Recent",
|
||||
"user_list": get_online_status(request.profile, recent_list, recent_rooms),
|
||||
},
|
||||
{
|
||||
"title": _("Admin"),
|
||||
"user_list": get_online_status(profile, admin_list),
|
||||
"title": "Following",
|
||||
"user_list": get_online_status(request.profile, friend_list),
|
||||
},
|
||||
{
|
||||
"title": "Admin",
|
||||
"user_list": get_online_status(request.profile, admin_list),
|
||||
},
|
||||
{
|
||||
"title": "Other",
|
||||
"user_list": get_online_status(request.profile, all_user_status),
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -463,7 +393,7 @@ def online_status_ajax(request):
|
|||
request,
|
||||
"chat/online_status.html",
|
||||
{
|
||||
"status_sections": get_status_context(request.profile),
|
||||
"status_sections": get_status_context(request),
|
||||
"unread_count_lobby": get_unread_count(None, request.profile),
|
||||
},
|
||||
)
|
||||
|
@ -487,6 +417,7 @@ def get_or_create_room(request):
|
|||
return HttpResponseBadRequest()
|
||||
|
||||
request_id, other_id = decrypt_url(decrypted_other_id)
|
||||
|
||||
if not other_id or not request_id or request_id != request.profile.id:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
@ -507,36 +438,55 @@ def get_or_create_room(request):
|
|||
user_room.last_seen = timezone.now()
|
||||
user_room.save()
|
||||
|
||||
room_url = reverse("chat", kwargs={"room_id": room.id})
|
||||
if request.method == "GET":
|
||||
return JsonResponse(
|
||||
{
|
||||
"room": room.id,
|
||||
"other_user_id": other_user.id,
|
||||
"url": room_url,
|
||||
}
|
||||
)
|
||||
return HttpResponseRedirect(room_url)
|
||||
return JsonResponse({"room": room.id, "other_user_id": other_user.id})
|
||||
return HttpResponseRedirect(reverse("chat", kwargs={"room_id": room.id}))
|
||||
|
||||
|
||||
def get_unread_count(rooms, user):
|
||||
if rooms:
|
||||
return UserRoom.objects.filter(
|
||||
user=user, room__in=rooms, unread_count__gt=0
|
||||
).values("unread_count", "room")
|
||||
else: # lobby
|
||||
user_room = UserRoom.objects.filter(user=user, room__isnull=True).first()
|
||||
if not user_room:
|
||||
return 0
|
||||
last_seen = user_room.last_seen
|
||||
res = (
|
||||
Message.objects.filter(room__isnull=True, time__gte=last_seen)
|
||||
mess = (
|
||||
Message.objects.filter(
|
||||
room=OuterRef("room"), time__gte=OuterRef("last_seen")
|
||||
)
|
||||
.exclude(author=user)
|
||||
.exclude(hidden=True)
|
||||
.count()
|
||||
.order_by()
|
||||
.values("room")
|
||||
.annotate(unread_count=Count("pk"))
|
||||
.values("unread_count")
|
||||
)
|
||||
|
||||
return res
|
||||
return (
|
||||
UserRoom.objects.filter(user=user, room__in=rooms)
|
||||
.annotate(
|
||||
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
|
||||
other_user=Case(
|
||||
When(room__user_one=user, then="room__user_two"),
|
||||
default="room__user_one",
|
||||
),
|
||||
)
|
||||
.filter(unread_count__gte=1)
|
||||
.values("other_user", "unread_count")
|
||||
)
|
||||
else: # lobby
|
||||
mess = (
|
||||
Message.objects.filter(room__isnull=True, time__gte=OuterRef("last_seen"))
|
||||
.exclude(author=user)
|
||||
.order_by()
|
||||
.values("room")
|
||||
.annotate(unread_count=Count("pk"))
|
||||
.values("unread_count")
|
||||
)
|
||||
|
||||
res = (
|
||||
UserRoom.objects.filter(user=user, room__isnull=True)
|
||||
.annotate(
|
||||
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
|
||||
)
|
||||
.values_list("unread_count", flat=True)
|
||||
)
|
||||
|
||||
return res[0] if len(res) else 0
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -552,3 +502,32 @@ def toggle_ignore(request, **kwargs):
|
|||
Ignore.toggle_ignore(request.profile, other_user)
|
||||
next_url = request.GET.get("next", "/")
|
||||
return HttpResponseRedirect(next_url)
|
||||
|
||||
|
||||
@login_required
|
||||
def get_unread_boxes(request):
|
||||
if request.method != "GET":
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
ignored_users = Ignore.get_ignored_users(request.profile)
|
||||
|
||||
mess = (
|
||||
Message.objects.filter(room=OuterRef("room"), time__gte=OuterRef("last_seen"))
|
||||
.exclude(author=request.profile)
|
||||
.exclude(author__in=ignored_users)
|
||||
.order_by()
|
||||
.values("room")
|
||||
.annotate(unread_count=Count("pk"))
|
||||
.values("unread_count")
|
||||
)
|
||||
|
||||
unread_boxes = (
|
||||
UserRoom.objects.filter(user=request.profile, room__isnull=False)
|
||||
.annotate(
|
||||
unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0),
|
||||
)
|
||||
.filter(unread_count__gte=1)
|
||||
.count()
|
||||
)
|
||||
|
||||
return JsonResponse({"unread_boxes": unread_boxes})
|
||||
|
|
|
@ -11,12 +11,6 @@
|
|||
bottom: 0;
|
||||
}
|
||||
|
||||
.ace_editor {
|
||||
overflow: hidden;
|
||||
font: 12px/normal 'Fira Code', 'Monaco', 'Menlo', monospace;
|
||||
direction: 1tr;
|
||||
}
|
||||
|
||||
.django-ace-widget.loading {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -77,17 +77,15 @@
|
|||
mode = widget.getAttribute('data-mode'),
|
||||
theme = widget.getAttribute('data-theme'),
|
||||
wordwrap = widget.getAttribute('data-wordwrap'),
|
||||
toolbar = prev(widget);
|
||||
var main_block = div.parentNode.parentNode;
|
||||
toolbar = prev(widget),
|
||||
main_block = toolbar.parentNode;
|
||||
|
||||
if (toolbar != null) {
|
||||
// Toolbar maximize/minimize button
|
||||
var min_max = toolbar.getElementsByClassName('django-ace-max_min');
|
||||
min_max[0].onclick = function () {
|
||||
minimizeMaximize(widget, main_block, editor);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
// Toolbar maximize/minimize button
|
||||
var min_max = toolbar.getElementsByClassName('django-ace-max_min');
|
||||
min_max[0].onclick = function () {
|
||||
minimizeMaximize(widget, main_block, editor);
|
||||
return false;
|
||||
};
|
||||
|
||||
editor.getSession().setValue(textarea.value);
|
||||
|
||||
|
@ -162,7 +160,7 @@
|
|||
]);
|
||||
|
||||
window[widget.id] = editor;
|
||||
setTimeout(() => $(widget).trigger('ace_load', [editor]), 100);
|
||||
$(widget).trigger('ace_load', [editor]);
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
|
|
@ -19,7 +19,6 @@ class AceWidget(forms.Textarea):
|
|||
width="100%",
|
||||
height="300px",
|
||||
no_ace_media=False,
|
||||
toolbar=True,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
|
@ -29,7 +28,6 @@ class AceWidget(forms.Textarea):
|
|||
self.width = width
|
||||
self.height = height
|
||||
self.ace_media = not no_ace_media
|
||||
self.toolbar = toolbar
|
||||
super(AceWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
@ -63,14 +61,10 @@ class AceWidget(forms.Textarea):
|
|||
|
||||
html = "<div%s><div></div></div>%s" % (flatatt(ace_attrs), textarea)
|
||||
|
||||
if self.toolbar:
|
||||
toolbar = (
|
||||
'<div style="width: {}" class="django-ace-toolbar">'
|
||||
'<a href="#" class="django-ace-max_min"></a>'
|
||||
"</div>"
|
||||
).format(self.width)
|
||||
html = toolbar + html
|
||||
|
||||
html = '<div class="django-ace-editor">{}</div>'.format(html)
|
||||
# add toolbar
|
||||
html = (
|
||||
'<div class="django-ace-editor"><div style="width: 100%%" class="django-ace-toolbar">'
|
||||
'<a href="./" class="django-ace-max_min"></a></div>%s</div>'
|
||||
) % html
|
||||
|
||||
return mark_safe(html)
|
||||
|
|
14
dmoj/decorators.py
Normal file
14
dmoj/decorators.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
import time
|
||||
|
||||
|
||||
def timeit(method):
|
||||
def timed(*args, **kw):
|
||||
ts = time.time()
|
||||
result = method(*args, **kw)
|
||||
te = time.time()
|
||||
if "log_time" in kw:
|
||||
name = kw.get("log_name", method.__name__.upper())
|
||||
kw["log_time"][name] = int((te - ts) * 1000)
|
||||
return result
|
||||
|
||||
return timed
|
123
dmoj/settings.py
123
dmoj/settings.py
|
@ -33,7 +33,6 @@ SITE_ID = 1
|
|||
SITE_NAME = "LQDOJ"
|
||||
SITE_LONG_NAME = "LQDOJ: Le Quy Don Online Judge"
|
||||
SITE_ADMIN_EMAIL = False
|
||||
SITE_DOMAIN = "lqdoj.edu.vn"
|
||||
|
||||
DMOJ_REQUIRE_STAFF_2FA = True
|
||||
|
||||
|
@ -50,6 +49,10 @@ DMOJ_PP_BONUS_FUNCTION = lambda n: 300 * (1 - 0.997**n) # noqa: E731
|
|||
NODEJS = "/usr/bin/node"
|
||||
EXIFTOOL = "/usr/bin/exiftool"
|
||||
ACE_URL = "//cdnjs.cloudflare.com/ajax/libs/ace/1.1.3"
|
||||
SELECT2_JS_URL = "//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"
|
||||
DEFAULT_SELECT2_CSS = (
|
||||
"//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css"
|
||||
)
|
||||
|
||||
DMOJ_CAMO_URL = None
|
||||
DMOJ_CAMO_KEY = None
|
||||
|
@ -61,7 +64,6 @@ DMOJ_PROBLEM_MAX_TIME_LIMIT = 60 # seconds
|
|||
DMOJ_PROBLEM_MIN_MEMORY_LIMIT = 0 # kilobytes
|
||||
DMOJ_PROBLEM_MAX_MEMORY_LIMIT = 1048576 # kilobytes
|
||||
DMOJ_PROBLEM_MIN_PROBLEM_POINTS = 0
|
||||
DMOJ_SUBMISSION_ROOT = "/tmp"
|
||||
DMOJ_RATING_COLORS = True
|
||||
DMOJ_EMAIL_THROTTLING = (10, 60)
|
||||
DMOJ_STATS_LANGUAGE_THRESHOLD = 10
|
||||
|
@ -73,7 +75,6 @@ DMOJ_BLOG_NEW_CONTEST_COUNT = 7
|
|||
DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT = 7
|
||||
DMOJ_TOTP_TOLERANCE_HALF_MINUTES = 1
|
||||
DMOJ_USER_MAX_ORGANIZATION_COUNT = 10
|
||||
DMOJ_USER_MAX_ORGANIZATION_ADD = 5
|
||||
DMOJ_COMMENT_VOTE_HIDE_THRESHOLD = -5
|
||||
DMOJ_PDF_PROBLEM_CACHE = ""
|
||||
DMOJ_PDF_PROBLEM_TEMP_DIR = tempfile.gettempdir()
|
||||
|
@ -84,9 +85,6 @@ DMOJ_STATS_SUBMISSION_RESULT_COLORS = {
|
|||
"CE": "#42586d",
|
||||
"ERR": "#ffa71c",
|
||||
}
|
||||
DMOJ_PROFILE_IMAGE_ROOT = "profile_images"
|
||||
DMOJ_ORGANIZATION_IMAGE_ROOT = "organization_images"
|
||||
DMOJ_TEST_FORMATTER_ROOT = "test_formatter"
|
||||
|
||||
MARKDOWN_STYLES = {}
|
||||
MARKDOWN_DEFAULT_STYLE = {}
|
||||
|
@ -103,6 +101,7 @@ MATHOID_CACHE_URL = False
|
|||
TEXOID_GZIP = False
|
||||
TEXOID_META_CACHE = "default"
|
||||
TEXOID_META_CACHE_TTL = 86400
|
||||
DMOJ_NEWSLETTER_ID_ON_REGISTER = 1
|
||||
|
||||
BAD_MAIL_PROVIDERS = ()
|
||||
BAD_MAIL_PROVIDER_REGEX = ()
|
||||
|
@ -132,10 +131,13 @@ USE_SELENIUM = False
|
|||
SELENIUM_CUSTOM_CHROME_PATH = None
|
||||
SELENIUM_CHROMEDRIVER_PATH = "chromedriver"
|
||||
|
||||
PYGMENT_THEME = "pygment-github.css"
|
||||
INLINE_JQUERY = True
|
||||
INLINE_FONTAWESOME = True
|
||||
JQUERY_JS = "//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"
|
||||
FONTAWESOME_CSS = "//cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
||||
FONTAWESOME_CSS = (
|
||||
"//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
|
||||
)
|
||||
DMOJ_CANONICAL = ""
|
||||
|
||||
# Application definition
|
||||
|
@ -169,7 +171,7 @@ else:
|
|||
},
|
||||
{
|
||||
"model": "judge.Submission",
|
||||
"icon": "fa-check-square",
|
||||
"icon": "fa-check-square-o",
|
||||
"children": [
|
||||
"judge.Language",
|
||||
"judge.Judge",
|
||||
|
@ -210,8 +212,10 @@ else:
|
|||
],
|
||||
},
|
||||
("judge.BlogPost", "fa-rss-square"),
|
||||
("judge.Comment", "fa-comment-o"),
|
||||
("judge.Ticket", "fa-exclamation-circle"),
|
||||
("admin.LogEntry", "fa-empire"),
|
||||
("flatpages.FlatPage", "fa-file-text-o"),
|
||||
("judge.Solution", "fa-pencil"),
|
||||
],
|
||||
"dashboard": {
|
||||
"breadcrumbs": True,
|
||||
|
@ -245,15 +249,15 @@ INSTALLED_APPS += (
|
|||
"impersonate",
|
||||
"django_jinja",
|
||||
"chat_box",
|
||||
"newsletter",
|
||||
"django.forms",
|
||||
)
|
||||
|
||||
MIDDLEWARE = (
|
||||
"judge.middleware.SlowRequestMiddleware",
|
||||
"judge.middleware.ShortCircuitMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"judge.middleware.DMOJLoginMiddleware",
|
||||
|
@ -264,28 +268,22 @@ MIDDLEWARE = (
|
|||
"impersonate.middleware.ImpersonateMiddleware",
|
||||
"judge.middleware.DMOJImpersonationMiddleware",
|
||||
"judge.middleware.ContestMiddleware",
|
||||
"judge.middleware.DarkModeMiddleware",
|
||||
"judge.middleware.SubdomainMiddleware",
|
||||
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
|
||||
"judge.social_auth.SocialAuthExceptionMiddleware",
|
||||
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
|
||||
)
|
||||
|
||||
X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||
|
||||
LANGUAGE_COOKIE_AGE = 8640000
|
||||
|
||||
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
|
||||
|
||||
IMPERSONATE = {
|
||||
"REQUIRE_SUPERUSER": True,
|
||||
"DISABLE_LOGGING": True,
|
||||
"ADMIN_DELETE_PERMISSION": True,
|
||||
}
|
||||
IMPERSONATE_REQUIRE_SUPERUSER = True
|
||||
IMPERSONATE_DISABLE_LOGGING = True
|
||||
|
||||
ACCOUNT_ACTIVATION_DAYS = 7
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
|
@ -325,6 +323,7 @@ TEMPLATES = [
|
|||
"judge.template_context.site",
|
||||
"judge.template_context.site_name",
|
||||
"judge.template_context.misc_config",
|
||||
"judge.template_context.math_setting",
|
||||
"social_django.context_processors.backends",
|
||||
"social_django.context_processors.login_redirect",
|
||||
],
|
||||
|
@ -363,10 +362,60 @@ LOCALE_PATHS = [
|
|||
]
|
||||
|
||||
LANGUAGES = [
|
||||
("vi", _("Vietnamese")),
|
||||
("de", _("German")),
|
||||
("en", _("English")),
|
||||
("es", _("Spanish")),
|
||||
("fr", _("French")),
|
||||
("hr", _("Croatian")),
|
||||
("hu", _("Hungarian")),
|
||||
("ja", _("Japanese")),
|
||||
("ko", _("Korean")),
|
||||
("pt", _("Brazilian Portuguese")),
|
||||
("ro", _("Romanian")),
|
||||
("ru", _("Russian")),
|
||||
("sr-latn", _("Serbian (Latin)")),
|
||||
("tr", _("Turkish")),
|
||||
("vi", _("Vietnamese")),
|
||||
("zh-hans", _("Simplified Chinese")),
|
||||
("zh-hant", _("Traditional Chinese")),
|
||||
]
|
||||
|
||||
MARKDOWN_ADMIN_EDITABLE_STYLE = {
|
||||
"safe_mode": False,
|
||||
"use_camo": True,
|
||||
"texoid": True,
|
||||
"math": True,
|
||||
}
|
||||
|
||||
MARKDOWN_DEFAULT_STYLE = {
|
||||
"safe_mode": True,
|
||||
"nofollow": True,
|
||||
"use_camo": True,
|
||||
"math": True,
|
||||
}
|
||||
|
||||
MARKDOWN_USER_LARGE_STYLE = {
|
||||
"safe_mode": True,
|
||||
"nofollow": True,
|
||||
"use_camo": True,
|
||||
"math": True,
|
||||
}
|
||||
|
||||
MARKDOWN_STYLES = {
|
||||
"comment": MARKDOWN_DEFAULT_STYLE,
|
||||
"self-description": MARKDOWN_USER_LARGE_STYLE,
|
||||
"problem": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"contest": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"language": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"license": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"judge": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"blog": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"solution": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"contest_tag": MARKDOWN_ADMIN_EDITABLE_STYLE,
|
||||
"organization-about": MARKDOWN_USER_LARGE_STYLE,
|
||||
"ticket": MARKDOWN_USER_LARGE_STYLE,
|
||||
}
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
|
||||
|
@ -384,7 +433,6 @@ BRIDGED_JUDGE_ADDRESS = [("localhost", 9999)]
|
|||
BRIDGED_JUDGE_PROXIES = None
|
||||
BRIDGED_DJANGO_ADDRESS = [("localhost", 9998)]
|
||||
BRIDGED_DJANGO_CONNECT = None
|
||||
BRIDGED_AUTO_CREATE_JUDGE = False
|
||||
|
||||
# Event Server configuration
|
||||
EVENT_DAEMON_USE = False
|
||||
|
@ -432,7 +480,7 @@ AUTHENTICATION_BACKENDS = (
|
|||
"social_core.backends.google.GoogleOAuth2",
|
||||
"social_core.backends.facebook.FacebookOAuth2",
|
||||
"judge.social_auth.GitHubSecureEmailOAuth2",
|
||||
"judge.authentication.CustomModelBackend",
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
)
|
||||
|
||||
SOCIAL_AUTH_PIPELINE = (
|
||||
|
@ -473,26 +521,19 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
|||
|
||||
MESSAGES_TO_LOAD = 15
|
||||
|
||||
ML_OUTPUT_PATH = None
|
||||
NEWSLETTER_CONFIRM_EMAIL = False
|
||||
|
||||
# Use subdomain for organizations
|
||||
USE_SUBDOMAIN = False
|
||||
# Amount of seconds to wait between each email. Here 100ms is used.
|
||||
NEWSLETTER_EMAIL_DELAY = 0.1
|
||||
|
||||
# Chat
|
||||
CHAT_SECRET_KEY = "QUdVFsxk6f5-Hd8g9BXv81xMqvIZFRqMl-KbRzztW-U="
|
||||
# Amount of seconds to wait between each batch. Here one minute is used.
|
||||
NEWSLETTER_BATCH_DELAY = 60
|
||||
|
||||
# Nginx
|
||||
META_REMOTE_ADDRESS_KEY = "REMOTE_ADDR"
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
|
||||
# Chunk upload
|
||||
CHUNK_UPLOAD_DIR = "/tmp/chunk_upload_tmp"
|
||||
|
||||
# Rate limit
|
||||
RL_VOTE = "200/h"
|
||||
RL_COMMENT = "30/h"
|
||||
# Number of emails in one batch
|
||||
NEWSLETTER_BATCH_SIZE = 100
|
||||
|
||||
# Google form to request name
|
||||
REGISTER_NAME_URL = None
|
||||
|
||||
try:
|
||||
with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f:
|
||||
|
|
330
dmoj/urls.py
330
dmoj/urls.py
|
@ -12,9 +12,16 @@ from django.utils.functional import lazystr
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import RedirectView
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf.urls.static import static as url_static
|
||||
|
||||
|
||||
from judge.feed import (
|
||||
AtomBlogFeed,
|
||||
AtomCommentFeed,
|
||||
AtomProblemFeed,
|
||||
BlogFeed,
|
||||
CommentFeed,
|
||||
ProblemFeed,
|
||||
)
|
||||
from judge.forms import CustomAuthenticationForm
|
||||
from judge.sitemap import (
|
||||
BlogPostSitemap,
|
||||
|
@ -36,8 +43,6 @@ from judge.views import (
|
|||
language,
|
||||
license,
|
||||
mailgun,
|
||||
markdown_editor,
|
||||
test_formatter,
|
||||
notification,
|
||||
organization,
|
||||
preview,
|
||||
|
@ -53,19 +58,9 @@ from judge.views import (
|
|||
totp,
|
||||
user,
|
||||
volunteer,
|
||||
pagevote,
|
||||
bookmark,
|
||||
widgets,
|
||||
internal,
|
||||
resolver,
|
||||
course,
|
||||
email,
|
||||
custom_file_upload,
|
||||
)
|
||||
from judge import authentication
|
||||
|
||||
from judge.views.test_formatter import test_formatter
|
||||
|
||||
from judge.views.problem_data import (
|
||||
ProblemDataView,
|
||||
ProblemSubmissionDiff,
|
||||
|
@ -77,6 +72,7 @@ from judge.views.register import ActivationView, RegistrationView
|
|||
from judge.views.select2 import (
|
||||
AssigneeSelect2View,
|
||||
ChatUserSearchSelect2View,
|
||||
CommentSelect2View,
|
||||
ContestSelect2View,
|
||||
ContestUserSearchSelect2View,
|
||||
OrganizationSelect2View,
|
||||
|
@ -84,7 +80,6 @@ from judge.views.select2 import (
|
|||
TicketUserSelect2View,
|
||||
UserSearchSelect2View,
|
||||
UserSelect2View,
|
||||
ProblemAuthorSearchSelect2View,
|
||||
)
|
||||
|
||||
admin.autodiscover()
|
||||
|
@ -104,19 +99,19 @@ register_patterns = [
|
|||
# confusing 404.
|
||||
url(
|
||||
r"^activate/(?P<activation_key>\w+)/$",
|
||||
ActivationView.as_view(title=_("Activation key invalid")),
|
||||
ActivationView.as_view(title="Activation key invalid"),
|
||||
name="registration_activate",
|
||||
),
|
||||
url(
|
||||
r"^register/$",
|
||||
RegistrationView.as_view(title=_("Register")),
|
||||
RegistrationView.as_view(title="Register"),
|
||||
name="registration_register",
|
||||
),
|
||||
url(
|
||||
r"^register/complete/$",
|
||||
TitledTemplateView.as_view(
|
||||
template_name="registration/registration_complete.html",
|
||||
title=_("Registration Completed"),
|
||||
title="Registration Completed",
|
||||
),
|
||||
name="registration_complete",
|
||||
),
|
||||
|
@ -124,7 +119,7 @@ register_patterns = [
|
|||
r"^register/closed/$",
|
||||
TitledTemplateView.as_view(
|
||||
template_name="registration/registration_closed.html",
|
||||
title=_("Registration not allowed"),
|
||||
title="Registration not allowed",
|
||||
),
|
||||
name="registration_disallowed",
|
||||
),
|
||||
|
@ -141,7 +136,9 @@ register_patterns = [
|
|||
url(r"^logout/$", user.UserLogoutView.as_view(), name="auth_logout"),
|
||||
url(
|
||||
r"^password/change/$",
|
||||
authentication.CustomPasswordChangeView.as_view(),
|
||||
auth_views.PasswordChangeView.as_view(
|
||||
template_name="registration/password_change_form.html",
|
||||
),
|
||||
name="password_change",
|
||||
),
|
||||
url(
|
||||
|
@ -181,17 +178,6 @@ register_patterns = [
|
|||
),
|
||||
name="password_reset_done",
|
||||
),
|
||||
url(r"^email/change/$", email.email_change_view, name="email_change"),
|
||||
url(
|
||||
r"^email/change/verify/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$",
|
||||
email.verify_email_view,
|
||||
name="email_change_verify",
|
||||
),
|
||||
url(
|
||||
r"^email/change/pending$",
|
||||
email.email_change_pending_view,
|
||||
name="email_change_pending",
|
||||
),
|
||||
url(r"^social/error/$", register.social_auth_error, name="social_auth_error"),
|
||||
url(r"^2fa/$", totp.TOTPLoginView.as_view(), name="login_2fa"),
|
||||
url(r"^2fa/enable/$", totp.TOTPEnableView.as_view(), name="enable_2fa"),
|
||||
|
@ -215,7 +201,6 @@ def paged_list_view(view, name, **kwargs):
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
url("", include("pagedown.urls")),
|
||||
url(
|
||||
r"^$",
|
||||
blog.PostList.as_view(template_name="home.html", title=_("Home")),
|
||||
|
@ -223,7 +208,6 @@ urlpatterns = [
|
|||
name="home",
|
||||
),
|
||||
url(r"^500/$", exception),
|
||||
url(r"^toggle_darkmode/$", user.toggle_darkmode, name="toggle_darkmode"),
|
||||
url(r"^admin/", admin.site.urls),
|
||||
url(r"^i18n/", include("django.conf.urls.i18n")),
|
||||
url(r"^accounts/", include(register_patterns)),
|
||||
|
@ -240,19 +224,18 @@ urlpatterns = [
|
|||
url(r"^problems/", paged_list_view(problem.ProblemList, "problem_list")),
|
||||
url(r"^problems/random/$", problem.RandomProblem.as_view(), name="problem_random"),
|
||||
url(
|
||||
r"^problems/feed/$",
|
||||
problem.ProblemFeed.as_view(feed_type="for_you"),
|
||||
name="problem_feed",
|
||||
r"^problems/feed/",
|
||||
paged_list_view(problem.ProblemFeed, "problem_feed", feed_type="for_you"),
|
||||
),
|
||||
url(
|
||||
r"^problems/feed/new/$",
|
||||
problem.ProblemFeed.as_view(feed_type="new"),
|
||||
name="problem_feed_new",
|
||||
r"^problems/feed/new/",
|
||||
paged_list_view(problem.ProblemFeed, "problem_feed_new", feed_type="new"),
|
||||
),
|
||||
url(
|
||||
r"^problems/feed/volunteer/$",
|
||||
problem.ProblemFeed.as_view(feed_type="volunteer"),
|
||||
name="problem_feed_volunteer",
|
||||
r"^problems/feed/volunteer/",
|
||||
paged_list_view(
|
||||
problem.ProblemFeed, "problem_feed_volunteer", feed_type="volunteer"
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^problem/(?P<problem>[^/]+)",
|
||||
|
@ -271,11 +254,6 @@ urlpatterns = [
|
|||
problem.ProblemPdfView.as_view(),
|
||||
name="problem_pdf",
|
||||
),
|
||||
url(
|
||||
r"^/pdf_description$",
|
||||
problem.ProblemPdfDescriptionView.as_view(),
|
||||
name="problem_pdf_description",
|
||||
),
|
||||
url(r"^/clone", problem.ProblemClone.as_view(), name="problem_clone"),
|
||||
url(r"^/submit$", problem.problem_submit, name="problem_submit"),
|
||||
url(
|
||||
|
@ -380,8 +358,9 @@ urlpatterns = [
|
|||
paged_list_view(submission.AllUserSubmissions, "all_user_submissions"),
|
||||
),
|
||||
url(
|
||||
r"^submissions/friends/",
|
||||
paged_list_view(submission.AllFriendSubmissions, "all_friend_submissions"),
|
||||
r"^src/(?P<submission>\d+)$",
|
||||
submission.SubmissionSource.as_view(),
|
||||
name="submission_source",
|
||||
),
|
||||
url(
|
||||
r"^src/(?P<submission>\d+)/raw$",
|
||||
|
@ -398,41 +377,10 @@ urlpatterns = [
|
|||
name="submission_status",
|
||||
),
|
||||
url(r"^/abort$", submission.abort_submission, name="submission_abort"),
|
||||
url(r"^/html$", submission.single_submission),
|
||||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^test_formatter/",
|
||||
include(
|
||||
[
|
||||
url(
|
||||
r"^$",
|
||||
login_required(test_formatter.TestFormatter.as_view()),
|
||||
name="test_formatter",
|
||||
),
|
||||
url(
|
||||
r"^edit_page$",
|
||||
login_required(test_formatter.EditTestFormatter.as_view()),
|
||||
name="test_formatter_edit",
|
||||
),
|
||||
url(
|
||||
r"^download_page$",
|
||||
login_required(test_formatter.DownloadTestFormatter.as_view()),
|
||||
name="test_formatter_download",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^markdown_editor/",
|
||||
markdown_editor.MarkdownEditor.as_view(),
|
||||
name="markdown_editor",
|
||||
),
|
||||
url(
|
||||
r"^submission_source_file/(?P<filename>(\w|\.)+)",
|
||||
submission.SubmissionSourceFileView.as_view(),
|
||||
name="submission_source_file",
|
||||
),
|
||||
url(
|
||||
r"^users/",
|
||||
include(
|
||||
|
@ -452,7 +400,6 @@ urlpatterns = [
|
|||
),
|
||||
url(r"^user$", user.UserAboutPage.as_view(), name="user_page"),
|
||||
url(r"^edit/profile/$", user.edit_profile, name="user_edit_profile"),
|
||||
url(r"^user/bookmarks", user.UserBookMarkPage.as_view(), name="user_bookmark"),
|
||||
url(
|
||||
r"^user/(?P<user>\w+)",
|
||||
include(
|
||||
|
@ -487,7 +434,6 @@ urlpatterns = [
|
|||
reverse("all_user_submissions", args=[user])
|
||||
),
|
||||
),
|
||||
url(r"^/toggle_follow/", user.toggle_follow, name="user_toggle_follow"),
|
||||
url(
|
||||
r"^/$",
|
||||
lambda _, user: HttpResponsePermanentRedirect(
|
||||
|
@ -497,15 +443,9 @@ urlpatterns = [
|
|||
]
|
||||
),
|
||||
),
|
||||
url(r"^pagevotes/upvote/$", pagevote.upvote_page, name="pagevote_upvote"),
|
||||
url(r"^pagevotes/downvote/$", pagevote.downvote_page, name="pagevote_downvote"),
|
||||
url(r"^bookmarks/dobookmark/$", bookmark.dobookmark_page, name="dobookmark"),
|
||||
url(r"^bookmarks/undobookmark/$", bookmark.undobookmark_page, name="undobookmark"),
|
||||
url(r"^comments/upvote/$", comment.upvote_comment, name="comment_upvote"),
|
||||
url(r"^comments/downvote/$", comment.downvote_comment, name="comment_downvote"),
|
||||
url(r"^comments/hide/$", comment.comment_hide, name="comment_hide"),
|
||||
url(r"^comments/get_replies/$", comment.get_replies, name="comment_get_replies"),
|
||||
url(r"^comments/show_more/$", comment.get_show_more, name="comment_show_more"),
|
||||
url(
|
||||
r"^comments/(?P<id>\d+)/",
|
||||
include(
|
||||
|
@ -535,58 +475,6 @@ urlpatterns = [
|
|||
),
|
||||
),
|
||||
url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")),
|
||||
url(
|
||||
r"^contests/summary/(?P<key>\w+)/",
|
||||
paged_list_view(contests.ContestsSummaryView, "contests_summary"),
|
||||
),
|
||||
url(
|
||||
r"^contests/official",
|
||||
paged_list_view(contests.OfficialContestList, "official_contest_list"),
|
||||
),
|
||||
url(r"^courses/", paged_list_view(course.CourseList, "course_list")),
|
||||
url(
|
||||
r"^course/(?P<slug>[\w-]*)",
|
||||
include(
|
||||
[
|
||||
url(r"^$", course.CourseDetail.as_view(), name="course_detail"),
|
||||
url(
|
||||
r"^/lesson/(?P<id>\d+)$",
|
||||
course.CourseLessonDetail.as_view(),
|
||||
name="course_lesson_detail",
|
||||
),
|
||||
url(
|
||||
r"^/edit_lessons$",
|
||||
course.EditCourseLessonsView.as_view(),
|
||||
name="edit_course_lessons",
|
||||
),
|
||||
url(
|
||||
r"^/grades$",
|
||||
course.CourseStudentResults.as_view(),
|
||||
name="course_grades",
|
||||
),
|
||||
url(
|
||||
r"^/grades/lesson/(?P<id>\d+)$",
|
||||
course.CourseStudentResultsLesson.as_view(),
|
||||
name="course_grades_lesson",
|
||||
),
|
||||
url(
|
||||
r"^/add_contest$",
|
||||
course.AddCourseContest.as_view(),
|
||||
name="add_course_contest",
|
||||
),
|
||||
url(
|
||||
r"^/edit_contest/(?P<contest>\w+)$",
|
||||
course.EditCourseContest.as_view(),
|
||||
name="edit_course_contest",
|
||||
),
|
||||
url(
|
||||
r"^/contests$",
|
||||
course.CourseContestList.as_view(),
|
||||
name="course_contest_list",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^contests/(?P<year>\d+)/(?P<month>\d+)/$",
|
||||
contests.ContestCalendar.as_view(),
|
||||
|
@ -625,13 +513,20 @@ urlpatterns = [
|
|||
name="contest_ranking",
|
||||
),
|
||||
url(
|
||||
r"^/final_ranking/$",
|
||||
contests.ContestFinalRanking.as_view(),
|
||||
name="contest_final_ranking",
|
||||
r"^/ranking/ajax$",
|
||||
contests.contest_ranking_ajax,
|
||||
name="contest_ranking_ajax",
|
||||
),
|
||||
url(r"^/join$", contests.ContestJoin.as_view(), name="contest_join"),
|
||||
url(r"^/leave$", contests.ContestLeave.as_view(), name="contest_leave"),
|
||||
url(r"^/stats$", contests.ContestStats.as_view(), name="contest_stats"),
|
||||
url(
|
||||
r"^/rank/(?P<problem>\w+)/",
|
||||
paged_list_view(
|
||||
ranked_submission.ContestRankedSubmission,
|
||||
"contest_ranked_submissions",
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)",
|
||||
paged_list_view(
|
||||
|
@ -639,19 +534,12 @@ urlpatterns = [
|
|||
),
|
||||
),
|
||||
url(
|
||||
r"^/submissions/(?P<participation>\d+)/(?P<problem>\w+)/ajax",
|
||||
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)/ajax",
|
||||
paged_list_view(
|
||||
submission.UserContestSubmissionsAjax,
|
||||
"contest_user_submissions_ajax",
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^/submissions",
|
||||
paged_list_view(
|
||||
submission.ContestSubmissions,
|
||||
"contest_submissions",
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^/participations$",
|
||||
contests.ContestParticipationList.as_view(),
|
||||
|
@ -691,11 +579,6 @@ urlpatterns = [
|
|||
organization.OrganizationList.as_view(),
|
||||
name="organization_list",
|
||||
),
|
||||
url(
|
||||
r"^organizations/add/$",
|
||||
organization.AddOrganization.as_view(),
|
||||
name="organization_add",
|
||||
),
|
||||
url(
|
||||
r"^organization/(?P<pk>\d+)-(?P<slug>[\w-]*)",
|
||||
include(
|
||||
|
@ -706,39 +589,19 @@ urlpatterns = [
|
|||
name="organization_home",
|
||||
),
|
||||
url(
|
||||
r"^/users/",
|
||||
paged_list_view(
|
||||
organization.OrganizationUsers,
|
||||
"organization_users",
|
||||
),
|
||||
r"^/users$",
|
||||
organization.OrganizationUsers.as_view(),
|
||||
name="organization_users",
|
||||
),
|
||||
url(
|
||||
r"^/problems/",
|
||||
paged_list_view(
|
||||
organization.OrganizationProblems, "organization_problems"
|
||||
),
|
||||
r"^/problems$",
|
||||
organization.OrganizationProblems.as_view(),
|
||||
name="organization_problems",
|
||||
),
|
||||
url(
|
||||
r"^/contests/",
|
||||
paged_list_view(
|
||||
organization.OrganizationContests, "organization_contests"
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^/contest/add",
|
||||
organization.AddOrganizationContest.as_view(),
|
||||
name="organization_contest_add",
|
||||
),
|
||||
url(
|
||||
r"^/contest/edit/(?P<contest>\w+)",
|
||||
organization.EditOrganizationContest.as_view(),
|
||||
name="organization_contest_edit",
|
||||
),
|
||||
url(
|
||||
r"^/submissions/",
|
||||
paged_list_view(
|
||||
organization.OrganizationSubmissions, "organization_submissions"
|
||||
),
|
||||
r"^/contests$",
|
||||
organization.OrganizationContests.as_view(),
|
||||
name="organization_contests",
|
||||
),
|
||||
url(
|
||||
r"^/join$",
|
||||
|
@ -847,7 +710,7 @@ urlpatterns = [
|
|||
]
|
||||
),
|
||||
),
|
||||
url(r"^blog/", blog.PostList.as_view(), name="blog_post_list"),
|
||||
url(r"^blog/", paged_list_view(blog.PostList, "blog_post_list")),
|
||||
url(r"^post/(?P<id>\d+)-(?P<slug>.*)$", blog.PostView.as_view(), name="blog_post"),
|
||||
url(r"^license/(?P<key>[-\w.]+)$", license.LicenseDetail.as_view(), name="license"),
|
||||
url(
|
||||
|
@ -917,11 +780,6 @@ urlpatterns = [
|
|||
AssigneeSelect2View.as_view(),
|
||||
name="ticket_assignee_select2_ajax",
|
||||
),
|
||||
url(
|
||||
r"^problem_authors$",
|
||||
ProblemAuthorSearchSelect2View.as_view(),
|
||||
name="problem_authors_select2_ajax",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
@ -980,6 +838,19 @@ urlpatterns = [
|
|||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^feed/",
|
||||
include(
|
||||
[
|
||||
url(r"^problems/rss/$", ProblemFeed(), name="problem_rss"),
|
||||
url(r"^problems/atom/$", AtomProblemFeed(), name="problem_atom"),
|
||||
url(r"^comment/rss/$", CommentFeed(), name="comment_rss"),
|
||||
url(r"^comment/atom/$", AtomCommentFeed(), name="comment_atom"),
|
||||
url(r"^blog/rss/$", BlogFeed(), name="blog_rss"),
|
||||
url(r"^blog/atom/$", AtomBlogFeed(), name="blog_atom"),
|
||||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^stats/",
|
||||
include(
|
||||
|
@ -988,19 +859,27 @@ urlpatterns = [
|
|||
"^language/",
|
||||
include(
|
||||
[
|
||||
url("^$", stats.language, name="language_stats"),
|
||||
url(
|
||||
"^$",
|
||||
stats.StatLanguage.as_view(),
|
||||
name="language_stats",
|
||||
"^data/all/$",
|
||||
stats.language_data,
|
||||
name="language_stats_data_all",
|
||||
),
|
||||
url(
|
||||
"^data/ac/$",
|
||||
stats.ac_language_data,
|
||||
name="language_stats_data_ac",
|
||||
),
|
||||
url(
|
||||
"^data/status/$",
|
||||
stats.status_data,
|
||||
name="stats_data_status",
|
||||
),
|
||||
url(
|
||||
"^data/ac_rate/$",
|
||||
stats.ac_rate,
|
||||
name="language_stats_data_ac_rate",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
"^site/",
|
||||
include(
|
||||
[
|
||||
url("^$", stats.StatSite.as_view(), name="site_stats"),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
@ -1080,6 +959,9 @@ urlpatterns = [
|
|||
url(
|
||||
r"^contest/$", ContestSelect2View.as_view(), name="contest_select2"
|
||||
),
|
||||
url(
|
||||
r"^comment/$", CommentSelect2View.as_view(), name="comment_select2"
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
@ -1115,7 +997,6 @@ urlpatterns = [
|
|||
name="chat",
|
||||
),
|
||||
url(r"^delete/$", chat.delete_message, name="delete_chat_message"),
|
||||
url(r"^mute/$", chat.mute_message, name="mute_chat_message"),
|
||||
url(r"^post/$", chat.post_message, name="post_chat_message"),
|
||||
url(r"^ajax$", chat.chat_message_ajax, name="chat_message_ajax"),
|
||||
url(
|
||||
|
@ -1143,6 +1024,11 @@ urlpatterns = [
|
|||
chat.toggle_ignore,
|
||||
name="toggle_ignore",
|
||||
),
|
||||
url(
|
||||
r"^get_unread_boxes$",
|
||||
chat.get_unread_boxes,
|
||||
name="get_unread_boxes",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
@ -1155,37 +1041,13 @@ urlpatterns = [
|
|||
internal.InternalProblem.as_view(),
|
||||
name="internal_problem",
|
||||
),
|
||||
url(
|
||||
r"^problem_votes$",
|
||||
internal.get_problem_votes,
|
||||
name="internal_problem_votes",
|
||||
),
|
||||
url(
|
||||
r"^request_time$",
|
||||
internal.InternalRequestTime.as_view(),
|
||||
name="internal_request_time",
|
||||
),
|
||||
url(
|
||||
r"^request_time_detail$",
|
||||
internal.InternalRequestTimeDetail.as_view(),
|
||||
name="internal_request_time_detail",
|
||||
),
|
||||
url(
|
||||
r"^internal_slow_request$",
|
||||
internal.InternalSlowRequest.as_view(),
|
||||
name="internal_slow_request",
|
||||
),
|
||||
url(
|
||||
r"^internal_slow_request_detail$",
|
||||
internal.InternalSlowRequestDetail.as_view(),
|
||||
name="internal_slow_request_detail",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
url(
|
||||
r"^notifications/",
|
||||
paged_list_view(notification.NotificationList, "notification"),
|
||||
login_required(notification.NotificationList.as_view()),
|
||||
name="notification",
|
||||
),
|
||||
url(
|
||||
r"^import_users/",
|
||||
|
@ -1214,12 +1076,7 @@ urlpatterns = [
|
|||
]
|
||||
),
|
||||
),
|
||||
url(r"^resolver/(?P<contest>\w+)", resolver.Resolver.as_view(), name="resolver"),
|
||||
url(r"^upload/$", custom_file_upload.file_upload, name="custom_file_upload"),
|
||||
] + url_static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
# if hasattr(settings, "INTERNAL_IPS"):
|
||||
# urlpatterns.append(url("__debug__/", include("debug_toolbar.urls")))
|
||||
]
|
||||
|
||||
favicon_paths = [
|
||||
"apple-touch-icon-180x180.png",
|
||||
|
@ -1243,7 +1100,6 @@ favicon_paths = [
|
|||
"favicon-32x32.png",
|
||||
"favicon-16x16.png",
|
||||
"android-chrome-192x192.png",
|
||||
"android-chrome-512x512.png",
|
||||
"android-chrome-48x48.png",
|
||||
"mstile-310x150.png",
|
||||
"apple-touch-icon-144x144.png",
|
||||
|
@ -1263,5 +1119,7 @@ handler404 = "judge.views.error.error404"
|
|||
handler403 = "judge.views.error.error403"
|
||||
handler500 = "judge.views.error.error500"
|
||||
|
||||
if "newsletter" in settings.INSTALLED_APPS:
|
||||
urlpatterns.append(url(r"^newsletter/", include("newsletter.urls")))
|
||||
if "impersonate" in settings.INSTALLED_APPS:
|
||||
urlpatterns.append(url(r"^impersonate/", include("impersonate.urls")))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import pymysql
|
||||
|
||||
pymysql.install_as_MySQLdb()
|
||||
pymysql.version_info = (1, 4, 0, "final", 0)
|
||||
pymysql.version_info = (1, 3, 13, "final", 0)
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin.models import LogEntry
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from judge.admin.comments import CommentAdmin
|
||||
from judge.admin.contest import (
|
||||
ContestAdmin,
|
||||
ContestParticipationAdmin,
|
||||
ContestTagAdmin,
|
||||
ContestsSummaryAdmin,
|
||||
)
|
||||
from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestTagAdmin
|
||||
from judge.admin.interface import (
|
||||
BlogPostAdmin,
|
||||
LicenseAdmin,
|
||||
|
@ -17,18 +11,12 @@ from judge.admin.interface import (
|
|||
)
|
||||
from judge.admin.organization import OrganizationAdmin, OrganizationRequestAdmin
|
||||
from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin
|
||||
from judge.admin.profile import ProfileAdmin, UserAdmin
|
||||
from judge.admin.profile import ProfileAdmin
|
||||
from judge.admin.runtime import JudgeAdmin, LanguageAdmin
|
||||
from judge.admin.submission import SubmissionAdmin
|
||||
from judge.admin.taxon import (
|
||||
ProblemGroupAdmin,
|
||||
ProblemTypeAdmin,
|
||||
OfficialContestCategoryAdmin,
|
||||
OfficialContestLocationAdmin,
|
||||
)
|
||||
from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin
|
||||
from judge.admin.ticket import TicketAdmin
|
||||
from judge.admin.volunteer import VolunteerProblemVoteAdmin
|
||||
from judge.admin.course import CourseAdmin
|
||||
from judge.models import (
|
||||
BlogPost,
|
||||
Comment,
|
||||
|
@ -51,10 +39,6 @@ from judge.models import (
|
|||
Submission,
|
||||
Ticket,
|
||||
VolunteerProblemVote,
|
||||
Course,
|
||||
ContestsSummary,
|
||||
OfficialContestCategory,
|
||||
OfficialContestLocation,
|
||||
)
|
||||
|
||||
|
||||
|
@ -80,9 +64,3 @@ admin.site.register(Profile, ProfileAdmin)
|
|||
admin.site.register(Submission, SubmissionAdmin)
|
||||
admin.site.register(Ticket, TicketAdmin)
|
||||
admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
|
||||
admin.site.register(Course, CourseAdmin)
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(ContestsSummary, ContestsSummaryAdmin)
|
||||
admin.site.register(OfficialContestCategory, OfficialContestCategoryAdmin)
|
||||
admin.site.register(OfficialContestLocation, OfficialContestLocationAdmin)
|
||||
|
|
|
@ -12,6 +12,7 @@ class CommentForm(ModelForm):
|
|||
class Meta:
|
||||
widgets = {
|
||||
"author": AdminHeavySelect2Widget(data_view="profile_select2"),
|
||||
"parent": AdminHeavySelect2Widget(data_view="comment_select2"),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets["body"] = HeavyPreviewAdminPageDownWidget(
|
||||
|
@ -21,24 +22,12 @@ class CommentForm(ModelForm):
|
|||
|
||||
class CommentAdmin(VersionAdmin):
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"author",
|
||||
"parent",
|
||||
"score",
|
||||
"hidden",
|
||||
"content_type",
|
||||
"object_id",
|
||||
)
|
||||
},
|
||||
),
|
||||
(None, {"fields": ("author", "page", "parent", "score", "hidden")}),
|
||||
("Content", {"fields": ("body",)}),
|
||||
)
|
||||
list_display = ["author", "linked_object", "time"]
|
||||
search_fields = ["author__user__username", "body"]
|
||||
readonly_fields = ["score", "parent"]
|
||||
list_display = ["author", "linked_page", "time"]
|
||||
search_fields = ["author__user__username", "page", "body"]
|
||||
readonly_fields = ["score"]
|
||||
actions = ["hide_comment", "unhide_comment"]
|
||||
list_filter = ["hidden"]
|
||||
actions_on_top = True
|
||||
|
@ -77,6 +66,16 @@ class CommentAdmin(VersionAdmin):
|
|||
|
||||
unhide_comment.short_description = _("Unhide comments")
|
||||
|
||||
def linked_page(self, obj):
|
||||
link = obj.link
|
||||
if link is not None:
|
||||
return format_html('<a href="{0}">{1}</a>', link, obj.page)
|
||||
else:
|
||||
return format_html("{0}", obj.page)
|
||||
|
||||
linked_page.short_description = _("Associated page")
|
||||
linked_page.admin_order_field = "page"
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(CommentAdmin, self).save_model(request, obj, form, change)
|
||||
if obj.hidden:
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib import admin
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import Q, TextField
|
||||
from django.forms import ModelForm, ModelMultipleChoiceField, TextInput
|
||||
from django.forms import ModelForm, ModelMultipleChoiceField
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
|
@ -14,14 +14,7 @@ from reversion.admin import VersionAdmin
|
|||
from reversion_compare.admin import CompareVersionAdmin
|
||||
|
||||
from django_ace import AceWidget
|
||||
from judge.models import (
|
||||
Contest,
|
||||
ContestProblem,
|
||||
ContestSubmission,
|
||||
Profile,
|
||||
Rating,
|
||||
OfficialContest,
|
||||
)
|
||||
from judge.models import Contest, ContestProblem, ContestSubmission, Profile, Rating
|
||||
from judge.ratings import rate_contest
|
||||
from judge.widgets import (
|
||||
AdminHeavySelect2MultipleWidget,
|
||||
|
@ -31,8 +24,6 @@ from judge.widgets import (
|
|||
AdminSelect2Widget,
|
||||
HeavyPreviewAdminPageDownWidget,
|
||||
)
|
||||
from judge.views.contests import recalculate_contest_summary_result
|
||||
from judge.utils.contest import maybe_trigger_contest_rescore
|
||||
|
||||
|
||||
class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
|
||||
|
@ -75,12 +66,7 @@ class ContestTagAdmin(admin.ModelAdmin):
|
|||
|
||||
class ContestProblemInlineForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"problem": AdminHeavySelect2Widget(data_view="problem_select2"),
|
||||
"hidden_subtasks": TextInput(attrs={"size": "3"}),
|
||||
"points": TextInput(attrs={"size": "1"}),
|
||||
"order": TextInput(attrs={"size": "1"}),
|
||||
}
|
||||
widgets = {"problem": AdminHeavySelect2Widget(data_view="problem_select2")}
|
||||
|
||||
|
||||
class ContestProblemInline(admin.TabularInline):
|
||||
|
@ -93,8 +79,7 @@ class ContestProblemInline(admin.TabularInline):
|
|||
"partial",
|
||||
"is_pretested",
|
||||
"max_submissions",
|
||||
"hidden_subtasks",
|
||||
"show_testcases",
|
||||
"output_prefix_override",
|
||||
"order",
|
||||
"rejudge_column",
|
||||
)
|
||||
|
@ -157,26 +142,6 @@ class ContestForm(ModelForm):
|
|||
)
|
||||
|
||||
|
||||
class OfficialContestInlineForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"category": AdminSelect2Widget,
|
||||
"location": AdminSelect2Widget,
|
||||
}
|
||||
|
||||
|
||||
class OfficialContestInline(admin.StackedInline):
|
||||
fields = (
|
||||
"category",
|
||||
"year",
|
||||
"location",
|
||||
)
|
||||
model = OfficialContest
|
||||
can_delete = True
|
||||
form = OfficialContestInlineForm
|
||||
extra = 0
|
||||
|
||||
|
||||
class ContestAdmin(CompareVersionAdmin):
|
||||
fieldsets = (
|
||||
(None, {"fields": ("key", "name", "authors", "curators", "testers")}),
|
||||
|
@ -187,18 +152,13 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
"is_visible",
|
||||
"use_clarifications",
|
||||
"hide_problem_tags",
|
||||
"public_scoreboard",
|
||||
"scoreboard_visibility",
|
||||
"run_pretests_only",
|
||||
"points_precision",
|
||||
"rate_limit",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Scheduling"),
|
||||
{"fields": ("start_time", "end_time", "time_limit", "freeze_after")},
|
||||
),
|
||||
(_("Scheduling"), {"fields": ("start_time", "end_time", "time_limit")}),
|
||||
(
|
||||
_("Details"),
|
||||
{
|
||||
|
@ -232,7 +192,9 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
{
|
||||
"fields": (
|
||||
"access_code",
|
||||
"is_private",
|
||||
"private_contestants",
|
||||
"is_organization_private",
|
||||
"organizations",
|
||||
"view_contest_scoreboard",
|
||||
)
|
||||
|
@ -251,7 +213,7 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
"user_count",
|
||||
)
|
||||
search_fields = ("key", "name")
|
||||
inlines = [ContestProblemInline, OfficialContestInline]
|
||||
inlines = [ContestProblemInline]
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = ContestForm
|
||||
|
@ -287,7 +249,9 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
readonly += ["access_code"]
|
||||
if not request.user.has_perm("judge.create_private_contest"):
|
||||
readonly += [
|
||||
"is_private",
|
||||
"private_contestants",
|
||||
"is_organization_private",
|
||||
"organizations",
|
||||
]
|
||||
if not request.user.has_perm("judge.change_contest_visibility"):
|
||||
|
@ -302,8 +266,8 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
"judge.change_contest_visibility"
|
||||
):
|
||||
if (
|
||||
not len(form.cleaned_data["organizations"]) > 0
|
||||
and not len(form.cleaned_data["private_contestants"]) > 0
|
||||
not form.cleaned_data["is_private"]
|
||||
and not form.cleaned_data["is_organization_private"]
|
||||
):
|
||||
raise PermissionDenied
|
||||
if not request.user.has_perm("judge.create_private_contest"):
|
||||
|
@ -311,14 +275,23 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
# We need this flag because `save_related` deals with the inlines, but does not know if we have already rescored
|
||||
self._rescored = False
|
||||
if form.changed_data and any(
|
||||
f in form.changed_data for f in ("format_config", "format_name")
|
||||
):
|
||||
self._rescore(obj.key)
|
||||
self._rescored = True
|
||||
|
||||
def save_related(self, request, form, formsets, change):
|
||||
super().save_related(request, form, formsets, change)
|
||||
# Only rescored if we did not already do so in `save_model`
|
||||
formset_changed = False
|
||||
if any(formset.has_changed() for formset in formsets):
|
||||
formset_changed = True
|
||||
|
||||
maybe_trigger_contest_rescore(form, form.instance, formset_changed)
|
||||
if not self._rescored and any(formset.has_changed() for formset in formsets):
|
||||
self._rescore(form.cleaned_data["key"])
|
||||
obj = form.instance
|
||||
obj.is_organization_private = obj.organizations.count() > 0
|
||||
obj.is_private = obj.private_contestants.count() > 0
|
||||
obj.save()
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if not request.user.has_perm("judge.edit_own_contest"):
|
||||
|
@ -327,6 +300,11 @@ class ContestAdmin(CompareVersionAdmin):
|
|||
return True
|
||||
return obj.is_editable_by(request.user)
|
||||
|
||||
def _rescore(self, contest_key):
|
||||
from judge.tasks import rescore_contest
|
||||
|
||||
transaction.on_commit(rescore_contest.s(contest_key).delay)
|
||||
|
||||
def make_visible(self, request, queryset):
|
||||
if not request.user.has_perm("judge.change_contest_visibility"):
|
||||
queryset = queryset.filter(
|
||||
|
@ -510,25 +488,3 @@ class ContestParticipationAdmin(admin.ModelAdmin):
|
|||
|
||||
show_virtual.short_description = _("virtual")
|
||||
show_virtual.admin_order_field = "virtual"
|
||||
|
||||
|
||||
class ContestsSummaryForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"contests": AdminHeavySelect2MultipleWidget(
|
||||
data_view="contest_select2", attrs={"style": "width: 100%"}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class ContestsSummaryAdmin(admin.ModelAdmin):
|
||||
fields = ("key", "contests", "scores")
|
||||
list_display = ("key",)
|
||||
search_fields = ("key", "contests__key")
|
||||
form = ContestsSummaryForm
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ContestsSummaryAdmin, self).save_model(request, obj, form, change)
|
||||
obj.refresh_from_db()
|
||||
obj.results = recalculate_contest_summary_result(request, obj)
|
||||
obj.save()
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||
from django.forms import ModelForm
|
||||
|
||||
from judge.models import Course, CourseRole
|
||||
from judge.widgets import AdminSelect2MultipleWidget
|
||||
from judge.widgets import (
|
||||
AdminHeavySelect2MultipleWidget,
|
||||
AdminHeavySelect2Widget,
|
||||
HeavyPreviewAdminPageDownWidget,
|
||||
AdminSelect2Widget,
|
||||
)
|
||||
|
||||
|
||||
class CourseRoleInlineForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"user": AdminHeavySelect2Widget(
|
||||
data_view="profile_select2", attrs={"style": "width: 100%"}
|
||||
),
|
||||
"role": AdminSelect2Widget,
|
||||
}
|
||||
|
||||
|
||||
class CourseRoleInline(admin.TabularInline):
|
||||
model = CourseRole
|
||||
extra = 1
|
||||
form = CourseRoleInlineForm
|
||||
|
||||
|
||||
class CourseForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"organizations": AdminHeavySelect2MultipleWidget(
|
||||
data_view="organization_select2"
|
||||
),
|
||||
"about": HeavyPreviewAdminPageDownWidget(
|
||||
preview=reverse_lazy("blog_preview")
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class CourseAdmin(admin.ModelAdmin):
|
||||
prepopulated_fields = {"slug": ("name",)}
|
||||
inlines = [
|
||||
CourseRoleInline,
|
||||
]
|
||||
list_display = ("name", "is_public", "is_open")
|
||||
search_fields = ("name",)
|
||||
form = CourseForm
|
|
@ -53,8 +53,7 @@ class NavigationBarAdmin(DraggableMPTTAdmin):
|
|||
class BlogPostForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BlogPostForm, self).__init__(*args, **kwargs)
|
||||
if "authors" in self.fields:
|
||||
self.fields["authors"].widget.can_add_related = False
|
||||
self.fields["authors"].widget.can_add_related = False
|
||||
|
||||
class Meta:
|
||||
widgets = {
|
||||
|
@ -177,12 +176,7 @@ class LogEntryAdmin(admin.ModelAdmin):
|
|||
"object_link",
|
||||
"diff_link",
|
||||
)
|
||||
search_fields = (
|
||||
"object_repr",
|
||||
"change_message",
|
||||
"user__username",
|
||||
"content_type__model",
|
||||
)
|
||||
search_fields = ("object_repr", "change_message")
|
||||
list_filter = (UserListFilter, "content_type")
|
||||
list_display_links = None
|
||||
actions = None
|
||||
|
|
|
@ -33,6 +33,7 @@ class OrganizationAdmin(VersionAdmin):
|
|||
"short_name",
|
||||
"is_open",
|
||||
"about",
|
||||
"logo_override_image",
|
||||
"slots",
|
||||
"registrant",
|
||||
"creation_date",
|
||||
|
@ -42,16 +43,14 @@ class OrganizationAdmin(VersionAdmin):
|
|||
"name",
|
||||
"short_name",
|
||||
"is_open",
|
||||
"creation_date",
|
||||
"slots",
|
||||
"registrant",
|
||||
"show_public",
|
||||
)
|
||||
search_fields = ("name", "short_name", "registrant__user__username")
|
||||
prepopulated_fields = {"slug": ("name",)}
|
||||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = OrganizationForm
|
||||
ordering = ["-creation_date"]
|
||||
|
||||
def show_public(self, obj):
|
||||
return format_html(
|
||||
|
|
|
@ -1,49 +1,41 @@
|
|||
from operator import attrgetter
|
||||
import statistics
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin, messages
|
||||
from django.db import transaction, IntegrityError
|
||||
from django.contrib import admin
|
||||
from django.db import transaction
|
||||
from django.db.models import Q, Avg, Count
|
||||
from django.db.models.aggregates import StdDev
|
||||
from django.forms import ModelForm, TextInput
|
||||
from django.forms import ModelForm
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||
from django_ace import AceWidget
|
||||
from django.utils import timezone
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from reversion.admin import VersionAdmin
|
||||
from reversion_compare.admin import CompareVersionAdmin
|
||||
|
||||
|
||||
from judge.models import (
|
||||
LanguageLimit,
|
||||
LanguageTemplate,
|
||||
Problem,
|
||||
ProblemClarification,
|
||||
ProblemTranslation,
|
||||
Profile,
|
||||
Solution,
|
||||
Notification,
|
||||
)
|
||||
from judge.models.notification import make_notification
|
||||
from judge.widgets import (
|
||||
AdminHeavySelect2MultipleWidget,
|
||||
AdminSelect2MultipleWidget,
|
||||
AdminSelect2Widget,
|
||||
CheckboxSelectMultipleWithSelectAll,
|
||||
HeavyPreviewAdminPageDownWidget,
|
||||
HeavyPreviewPageDownWidget,
|
||||
)
|
||||
from judge.utils.problems import user_editable_ids, user_tester_ids
|
||||
|
||||
MEMORY_UNITS = (("KB", "KB"), ("MB", "MB"))
|
||||
|
||||
|
||||
class ProblemForm(ModelForm):
|
||||
change_message = forms.CharField(
|
||||
max_length=256, label="Edit reason", required=False
|
||||
)
|
||||
memory_unit = forms.ChoiceField(choices=MEMORY_UNITS)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProblemForm, self).__init__(*args, **kwargs)
|
||||
|
@ -57,25 +49,6 @@ class ProblemForm(ModelForm):
|
|||
}
|
||||
)
|
||||
|
||||
def clean_code(self):
|
||||
code = self.cleaned_data.get("code")
|
||||
if self.instance.pk:
|
||||
return code
|
||||
|
||||
if Problem.objects.filter(code=code).exists():
|
||||
raise ValidationError(_("A problem with this code already exists."))
|
||||
|
||||
return code
|
||||
|
||||
def clean(self):
|
||||
memory_unit = self.cleaned_data.get("memory_unit", "KB")
|
||||
if memory_unit == "MB":
|
||||
self.cleaned_data["memory_limit"] *= 1024
|
||||
date = self.cleaned_data.get("date")
|
||||
if not date or date > timezone.now():
|
||||
self.cleaned_data["date"] = timezone.now()
|
||||
return self.cleaned_data
|
||||
|
||||
class Meta:
|
||||
widgets = {
|
||||
"authors": AdminHeavySelect2MultipleWidget(
|
||||
|
@ -95,7 +68,6 @@ class ProblemForm(ModelForm):
|
|||
),
|
||||
"types": AdminSelect2MultipleWidget,
|
||||
"group": AdminSelect2Widget,
|
||||
"memory_limit": TextInput(attrs={"size": "20"}),
|
||||
}
|
||||
if HeavyPreviewAdminPageDownWidget is not None:
|
||||
widgets["description"] = HeavyPreviewAdminPageDownWidget(
|
||||
|
@ -119,44 +91,30 @@ class ProblemCreatorListFilter(admin.SimpleListFilter):
|
|||
|
||||
|
||||
class LanguageLimitInlineForm(ModelForm):
|
||||
memory_unit = forms.ChoiceField(choices=MEMORY_UNITS, label=_("Memory unit"))
|
||||
|
||||
class Meta:
|
||||
widgets = {
|
||||
"language": AdminSelect2Widget,
|
||||
"memory_limit": TextInput(attrs={"size": "10"}),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
if not self.cleaned_data.get("language"):
|
||||
self.cleaned_data["DELETE"] = True
|
||||
if (
|
||||
self.cleaned_data.get("memory_limit")
|
||||
and self.cleaned_data.get("memory_unit") == "MB"
|
||||
):
|
||||
self.cleaned_data["memory_limit"] *= 1024
|
||||
return self.cleaned_data
|
||||
widgets = {"language": AdminSelect2Widget}
|
||||
|
||||
|
||||
class LanguageLimitInline(admin.TabularInline):
|
||||
model = LanguageLimit
|
||||
fields = ("language", "time_limit", "memory_limit", "memory_unit")
|
||||
fields = ("language", "time_limit", "memory_limit")
|
||||
form = LanguageLimitInlineForm
|
||||
extra = 0
|
||||
|
||||
|
||||
class LanguageTemplateInlineForm(ModelForm):
|
||||
class ProblemClarificationForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"language": AdminSelect2Widget,
|
||||
"source": AceWidget(width="600px", height="200px", toolbar=False),
|
||||
}
|
||||
if HeavyPreviewPageDownWidget is not None:
|
||||
widgets = {
|
||||
"description": HeavyPreviewPageDownWidget(
|
||||
preview=reverse_lazy("comment_preview")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class LanguageTemplateInline(admin.TabularInline):
|
||||
model = LanguageTemplate
|
||||
fields = ("language", "source")
|
||||
form = LanguageTemplateInlineForm
|
||||
class ProblemClarificationInline(admin.StackedInline):
|
||||
model = ProblemClarification
|
||||
fields = ("description",)
|
||||
form = ProblemClarificationForm
|
||||
extra = 0
|
||||
|
||||
|
||||
|
@ -211,13 +169,14 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
"code",
|
||||
"name",
|
||||
"is_public",
|
||||
"organizations",
|
||||
"is_manually_managed",
|
||||
"date",
|
||||
"authors",
|
||||
"curators",
|
||||
"testers",
|
||||
"is_organization_private",
|
||||
"organizations",
|
||||
"description",
|
||||
"pdf_description",
|
||||
"license",
|
||||
),
|
||||
},
|
||||
|
@ -228,7 +187,7 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
),
|
||||
(_("Taxonomy"), {"fields": ("types", "group")}),
|
||||
(_("Points"), {"fields": (("points", "partial"), "short_circuit")}),
|
||||
(_("Limits"), {"fields": ("time_limit", ("memory_limit", "memory_unit"))}),
|
||||
(_("Limits"), {"fields": ("time_limit", "memory_limit")}),
|
||||
(_("Language"), {"fields": ("allowed_languages",)}),
|
||||
(_("Justice"), {"fields": ("banned_users",)}),
|
||||
(_("History"), {"fields": ("change_message",)}),
|
||||
|
@ -237,12 +196,15 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
"code",
|
||||
"name",
|
||||
"show_authors",
|
||||
"date",
|
||||
"points",
|
||||
"vote_cnt",
|
||||
"vote_mean",
|
||||
"vote_median",
|
||||
"vote_std",
|
||||
"is_public",
|
||||
"show_public",
|
||||
]
|
||||
ordering = ["-date"]
|
||||
ordering = ["code"]
|
||||
search_fields = (
|
||||
"code",
|
||||
"name",
|
||||
|
@ -251,7 +213,7 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
)
|
||||
inlines = [
|
||||
LanguageLimitInline,
|
||||
LanguageTemplateInline,
|
||||
ProblemClarificationInline,
|
||||
ProblemSolutionInline,
|
||||
ProblemTranslationInline,
|
||||
]
|
||||
|
@ -278,6 +240,8 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
fields = self.readonly_fields
|
||||
if not request.user.has_perm("judge.change_public_visibility"):
|
||||
fields += ("is_public",)
|
||||
if not request.user.has_perm("judge.change_manually_managed"):
|
||||
fields += ("is_manually_managed",)
|
||||
return fields
|
||||
|
||||
def show_authors(self, obj):
|
||||
|
@ -331,6 +295,11 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
|
||||
def get_queryset(self, request):
|
||||
queryset = Problem.objects.prefetch_related("authors__user")
|
||||
queryset = queryset.annotate(
|
||||
_vote_mean=Avg("problem_points_votes__points"),
|
||||
_vote_std=StdDev("problem_points_votes__points"),
|
||||
_vote_cnt=Count("problem_points_votes__points"),
|
||||
)
|
||||
if request.user.has_perm("judge.edit_all_problem"):
|
||||
return queryset
|
||||
|
||||
|
@ -365,56 +334,12 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
return form
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
form.changed_data.remove("memory_unit")
|
||||
super().save_model(request, obj, form, change)
|
||||
if form.changed_data and any(
|
||||
f in form.changed_data for f in ("is_public", "points", "partial")
|
||||
):
|
||||
self._rescore(request, obj.id)
|
||||
|
||||
def save_related(self, request, form, formsets, change):
|
||||
editors = set()
|
||||
testers = set()
|
||||
if "curators" in form.changed_data or "authors" in form.changed_data:
|
||||
editors = set(form.instance.editor_ids)
|
||||
if "testers" in form.changed_data:
|
||||
testers = set(form.instance.tester_ids)
|
||||
|
||||
super().save_related(request, form, formsets, change)
|
||||
obj = form.instance
|
||||
obj.curators.add(request.profile)
|
||||
|
||||
if "curators" in form.changed_data or "authors" in form.changed_data:
|
||||
del obj.editor_ids
|
||||
editors = editors.union(set(obj.editor_ids))
|
||||
if "testers" in form.changed_data:
|
||||
del obj.tester_ids
|
||||
testers = testers.union(set(obj.tester_ids))
|
||||
|
||||
for editor in editors:
|
||||
user_editable_ids.dirty(editor)
|
||||
for tester in testers:
|
||||
user_tester_ids.dirty(tester)
|
||||
|
||||
# Create notification
|
||||
if "is_public" in form.changed_data or "organizations" in form.changed_data:
|
||||
users = set(obj.authors.all())
|
||||
users = users.union(users, set(obj.curators.all()))
|
||||
orgs = []
|
||||
if obj.organizations.count() > 0:
|
||||
for org in obj.organizations.all():
|
||||
users = users.union(users, set(org.admins.all()))
|
||||
orgs.append(org.name)
|
||||
else:
|
||||
admins = Profile.objects.filter(user__is_superuser=True).all()
|
||||
users = users.union(users, admins)
|
||||
link = reverse_lazy("admin:judge_problem_change", args=(obj.id,))
|
||||
html = f'<a href="{link}">{obj.name}</a>'
|
||||
category = "Problem public: " + str(obj.is_public)
|
||||
if orgs:
|
||||
category += " (" + ", ".join(orgs) + ")"
|
||||
make_notification(users, category, html, request.profile)
|
||||
|
||||
def construct_change_message(self, request, form, *args, **kwargs):
|
||||
if form.cleaned_data.get("change_message"):
|
||||
return form.cleaned_data["change_message"]
|
||||
|
@ -422,6 +347,25 @@ class ProblemAdmin(CompareVersionAdmin):
|
|||
request, form, *args, **kwargs
|
||||
)
|
||||
|
||||
def vote_mean(self, obj):
|
||||
return round(obj._vote_mean, 1) if obj._vote_mean is not None else None
|
||||
|
||||
vote_mean.admin_order_field = "_vote_mean"
|
||||
|
||||
def vote_std(self, obj):
|
||||
return round(obj._vote_std, 1) if obj._vote_std is not None else None
|
||||
|
||||
vote_std.admin_order_field = "_vote_std"
|
||||
|
||||
def vote_cnt(self, obj):
|
||||
return obj._vote_cnt
|
||||
|
||||
vote_cnt.admin_order_field = "_vote_cnt"
|
||||
|
||||
def vote_median(self, obj):
|
||||
votes = obj.problem_points_votes.values_list("points", flat=True)
|
||||
return statistics.median(votes) if votes else None
|
||||
|
||||
|
||||
class ProblemPointsVoteAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
from django.contrib import admin
|
||||
from django.forms import ModelForm, CharField, TextInput
|
||||
from django.forms import ModelForm
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||
from django.contrib.auth.admin import UserAdmin as OldUserAdmin
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.auth.forms import UserChangeForm
|
||||
|
||||
from django_ace import AceWidget
|
||||
|
||||
from judge.models import Profile, ProfileInfo
|
||||
from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
|
||||
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
import re
|
||||
from django_ace import AceWidget
|
||||
from judge.models import Profile
|
||||
from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
|
||||
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
|
@ -60,13 +53,6 @@ class TimezoneFilter(admin.SimpleListFilter):
|
|||
return queryset.filter(timezone=self.value())
|
||||
|
||||
|
||||
class ProfileInfoInline(admin.StackedInline):
|
||||
model = ProfileInfo
|
||||
can_delete = False
|
||||
verbose_name_plural = "profile info"
|
||||
fk_name = "profile"
|
||||
|
||||
|
||||
class ProfileAdmin(VersionAdmin):
|
||||
fields = (
|
||||
"user",
|
||||
|
@ -76,12 +62,15 @@ class ProfileAdmin(VersionAdmin):
|
|||
"timezone",
|
||||
"language",
|
||||
"ace_theme",
|
||||
"math_engine",
|
||||
"last_access",
|
||||
"ip",
|
||||
"mute",
|
||||
"is_unlisted",
|
||||
"is_banned_problem_voting",
|
||||
"notes",
|
||||
"is_totp_enabled",
|
||||
"user_script",
|
||||
"current_contest",
|
||||
)
|
||||
readonly_fields = ("user",)
|
||||
|
@ -102,7 +91,6 @@ class ProfileAdmin(VersionAdmin):
|
|||
actions_on_top = True
|
||||
actions_on_bottom = True
|
||||
form = ProfileForm
|
||||
inlines = (ProfileInfoInline,)
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(ProfileAdmin, self).get_queryset(request).select_related("user")
|
||||
|
@ -137,7 +125,7 @@ class ProfileAdmin(VersionAdmin):
|
|||
admin_user_admin.short_description = _("User")
|
||||
|
||||
def email(self, obj):
|
||||
return obj.email
|
||||
return obj.user.email
|
||||
|
||||
email.admin_order_field = "user__email"
|
||||
email.short_description = _("Email")
|
||||
|
@ -171,57 +159,11 @@ class ProfileAdmin(VersionAdmin):
|
|||
|
||||
recalculate_points.short_description = _("Recalculate scores")
|
||||
|
||||
|
||||
class UserForm(UserChangeForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["username"].help_text = _(
|
||||
"Username can only contain letters, digits, and underscores."
|
||||
)
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data.get("username")
|
||||
if not re.match(r"^\w+$", username):
|
||||
raise ValidationError(
|
||||
_("Username can only contain letters, digits, and underscores.")
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super(ProfileAdmin, self).get_form(request, obj, **kwargs)
|
||||
if "user_script" in form.base_fields:
|
||||
# form.base_fields['user_script'] does not exist when the user has only view permission on the model.
|
||||
form.base_fields["user_script"].widget = AceWidget(
|
||||
"javascript", request.profile.ace_theme
|
||||
)
|
||||
return username
|
||||
|
||||
|
||||
class UserAdmin(OldUserAdmin):
|
||||
# Customize the fieldsets for adding and editing users
|
||||
form = UserForm
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "password")}),
|
||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
||||
(
|
||||
"Permissions",
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
)
|
||||
},
|
||||
),
|
||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
|
||||
readonly_fields = ("last_login", "date_joined")
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = self.readonly_fields
|
||||
if not request.user.is_superuser:
|
||||
fields += (
|
||||
"is_staff",
|
||||
"is_active",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
)
|
||||
return fields
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
return form
|
||||
|
|
|
@ -194,16 +194,18 @@ class SubmissionAdmin(admin.ModelAdmin):
|
|||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if not request.user.has_perm("judge.edit_own_problem"):
|
||||
return False
|
||||
if request.user.has_perm("judge.edit_all_problem") or obj is None:
|
||||
return True
|
||||
return obj.problem.is_editor(request.profile)
|
||||
|
||||
def lookup_allowed(self, key, value):
|
||||
return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in (
|
||||
"problem__code",
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
if "case_points" in form.changed_data or "case_total" in form.changed_data:
|
||||
obj.update_contest()
|
||||
|
||||
def judge(self, request, queryset):
|
||||
if not request.user.has_perm(
|
||||
"judge.rejudge_submission"
|
||||
|
|
|
@ -56,11 +56,3 @@ class ProblemTypeAdmin(admin.ModelAdmin):
|
|||
[o.pk for o in obj.problem_set.all()] if obj else []
|
||||
)
|
||||
return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs)
|
||||
|
||||
|
||||
class OfficialContestCategoryAdmin(admin.ModelAdmin):
|
||||
fields = ("name",)
|
||||
|
||||
|
||||
class OfficialContestLocationAdmin(admin.ModelAdmin):
|
||||
fields = ("name",)
|
||||
|
|
|
@ -1,67 +1,34 @@
|
|||
from operator import attrgetter
|
||||
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
||||
from django.forms import ModelForm
|
||||
|
||||
from judge.models import VolunteerProblemVote
|
||||
from judge.widgets import AdminSelect2MultipleWidget
|
||||
|
||||
|
||||
class VolunteerProblemVoteForm(ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"types": AdminSelect2MultipleWidget,
|
||||
}
|
||||
|
||||
|
||||
class VolunteerProblemVoteAdmin(admin.ModelAdmin):
|
||||
form = VolunteerProblemVoteForm
|
||||
fields = (
|
||||
"voter",
|
||||
"problem",
|
||||
"time",
|
||||
"thinking_points",
|
||||
"knowledge_points",
|
||||
"feedback",
|
||||
)
|
||||
readonly_fields = ("time", "problem", "voter")
|
||||
list_display = (
|
||||
"voter",
|
||||
"problem_link",
|
||||
"time",
|
||||
"thinking_points",
|
||||
"knowledge_points",
|
||||
"types",
|
||||
"feedback",
|
||||
)
|
||||
readonly_fields = ("time", "problem_link", "voter")
|
||||
list_display = (
|
||||
"voter",
|
||||
"problem_link",
|
||||
"thinking_points",
|
||||
"knowledge_points",
|
||||
"show_types",
|
||||
"feedback",
|
||||
)
|
||||
search_fields = (
|
||||
"voter__user__username",
|
||||
"problem__code",
|
||||
"problem__name",
|
||||
)
|
||||
date_hierarchy = "time"
|
||||
|
||||
def problem_link(self, obj):
|
||||
if self.request.user.is_superuser:
|
||||
url = reverse("admin:judge_problem_change", args=(obj.problem.id,))
|
||||
else:
|
||||
url = reverse("problem_detail", args=(obj.problem.code,))
|
||||
return format_html(f"<a href='{url}'>{obj.problem}</a>")
|
||||
url = reverse("admin:judge_problem_change", args=(obj.problem.id,))
|
||||
return format_html(f"<a href='{url}'>{obj.problem.code}</a>")
|
||||
|
||||
problem_link.short_description = _("Problem")
|
||||
problem_link.admin_order_field = "problem__code"
|
||||
|
||||
def show_types(self, obj):
|
||||
return ", ".join(map(attrgetter("name"), obj.types.all()))
|
||||
|
||||
show_types.short_description = _("Types")
|
||||
|
||||
def get_queryset(self, request):
|
||||
self.request = request
|
||||
if request.user.is_superuser:
|
||||
return super().get_queryset(request)
|
||||
queryset = VolunteerProblemVote.objects.prefetch_related("voter")
|
||||
return queryset.filter(voter=request.profile).distinct()
|
||||
|
|
|
@ -12,7 +12,7 @@ class JudgeAppConfig(AppConfig):
|
|||
# OPERATIONS MAY HAVE SIDE EFFECTS.
|
||||
# DO NOT REMOVE THINKING THE IMPORT IS UNUSED.
|
||||
# noinspection PyUnresolvedReferences
|
||||
from . import models, signals, jinja2 # noqa: F401, imported for side effects
|
||||
from . import signals, jinja2 # noqa: F401, imported for side effects
|
||||
|
||||
from django.contrib.flatpages.models import FlatPage
|
||||
from django.contrib.flatpages.admin import FlatPageAdmin
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.contrib.auth.views import PasswordChangeView
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CustomModelBackend(ModelBackend):
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
try:
|
||||
# Check if the username is an email
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
# If the username is not an email, try authenticating with the username field
|
||||
user = User.objects.filter(email=username).first()
|
||||
|
||||
if user and user.check_password(password):
|
||||
return user
|
||||
|
||||
|
||||
class CustomPasswordChangeForm(PasswordChangeForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CustomPasswordChangeForm, self).__init__(*args, **kwargs)
|
||||
if not self.user.has_usable_password():
|
||||
self.fields.pop("old_password")
|
||||
|
||||
def clean_old_password(self):
|
||||
if "old_password" not in self.cleaned_data:
|
||||
return
|
||||
return super(CustomPasswordChangeForm, self).clean_old_password()
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CustomPasswordChangeForm, self).clean()
|
||||
if "old_password" not in self.cleaned_data and not self.errors:
|
||||
cleaned_data["old_password"] = ""
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class CustomPasswordChangeView(PasswordChangeView):
|
||||
form_class = CustomPasswordChangeForm
|
||||
success_url = reverse_lazy("password_change_done")
|
||||
template_name = "registration/password_change_form.html"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(CustomPasswordChangeView, self).get_form_kwargs()
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
|
@ -145,9 +145,6 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
|
|||
def on_timeout(self):
|
||||
pass
|
||||
|
||||
def on_cleanup(self):
|
||||
pass
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
tag = self.read_size()
|
||||
|
@ -207,8 +204,6 @@ class ZlibPacketHandler(metaclass=RequestHandlerMeta):
|
|||
if e.__class__.__name__ == "cancel_wait_ex":
|
||||
return
|
||||
raise
|
||||
finally:
|
||||
self.on_cleanup()
|
||||
|
||||
def send(self, data):
|
||||
compressed = zlib.compress(data.encode("utf-8"))
|
||||
|
|
|
@ -2,8 +2,6 @@ import json
|
|||
import logging
|
||||
import struct
|
||||
|
||||
from django import db
|
||||
|
||||
from judge.bridge.base_handler import Disconnect, ZlibPacketHandler
|
||||
|
||||
logger = logging.getLogger("judge.bridge")
|
||||
|
@ -62,5 +60,5 @@ class DjangoHandler(ZlibPacketHandler):
|
|||
def on_malformed(self, packet):
|
||||
logger.error("Malformed packet: %s", packet)
|
||||
|
||||
def on_cleanup(self):
|
||||
db.connection.close()
|
||||
def on_close(self):
|
||||
self._to_kill = False
|
||||
|
|
|
@ -10,11 +10,10 @@ from django import db
|
|||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.db.models import F
|
||||
from django.core.cache import cache
|
||||
|
||||
from judge import event_poster as event
|
||||
from judge.bridge.base_handler import ZlibPacketHandler, proxy_list
|
||||
from judge.utils.problems import finished_submission
|
||||
from judge.caching import finished_submission
|
||||
from judge.models import (
|
||||
Judge,
|
||||
Language,
|
||||
|
@ -24,8 +23,6 @@ from judge.models import (
|
|||
Submission,
|
||||
SubmissionTestCase,
|
||||
)
|
||||
from judge.bridge.utils import VanishedSubmission
|
||||
from judge.caching import cache_wrapper
|
||||
|
||||
logger = logging.getLogger("judge.bridge")
|
||||
json_log = logging.getLogger("judge.json.bridge")
|
||||
|
@ -39,7 +36,10 @@ SubmissionData = namedtuple(
|
|||
|
||||
|
||||
def _ensure_connection():
|
||||
db.connection.close_if_unusable_or_obsolete()
|
||||
try:
|
||||
db.connection.cursor().execute("SELECT 1").fetchall()
|
||||
except Exception:
|
||||
db.connection.close()
|
||||
|
||||
|
||||
class JudgeHandler(ZlibPacketHandler):
|
||||
|
@ -65,10 +65,10 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
"handshake": self.on_handshake,
|
||||
}
|
||||
self._working = False
|
||||
self._working_data = {}
|
||||
self._no_response_job = None
|
||||
self._problems = []
|
||||
self.executors = {}
|
||||
self.problems = set()
|
||||
self.problems = {}
|
||||
self.latency = None
|
||||
self.time_delta = None
|
||||
self.load = 1e100
|
||||
|
@ -94,6 +94,12 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
|
||||
def on_disconnect(self):
|
||||
self._stop_ping.set()
|
||||
if self._working:
|
||||
logger.error(
|
||||
"Judge %s disconnected while handling submission %s",
|
||||
self.name,
|
||||
self._working,
|
||||
)
|
||||
self.judges.remove(self)
|
||||
if self.name is not None:
|
||||
self._disconnected()
|
||||
|
@ -105,32 +111,24 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
self._make_json_log(action="disconnect", info="judge disconnected")
|
||||
)
|
||||
if self._working:
|
||||
self.judges.judge(
|
||||
self._working,
|
||||
self._working_data["problem"],
|
||||
self._working_data["language"],
|
||||
self._working_data["source"],
|
||||
None,
|
||||
0,
|
||||
Submission.objects.filter(id=self._working).update(
|
||||
status="IE", result="IE", error=""
|
||||
)
|
||||
json_log.error(
|
||||
self._make_json_log(
|
||||
sub=self._working,
|
||||
action="close",
|
||||
info="IE due to shutdown on grading",
|
||||
)
|
||||
)
|
||||
|
||||
def _authenticate(self, id, key):
|
||||
try:
|
||||
judge = Judge.objects.get(name=id)
|
||||
judge = Judge.objects.get(name=id, is_blocked=False)
|
||||
except Judge.DoesNotExist:
|
||||
if settings.BRIDGED_AUTO_CREATE_JUDGE:
|
||||
judge = Judge()
|
||||
judge.name = id
|
||||
judge.auth_key = key
|
||||
judge.save()
|
||||
result = True
|
||||
else:
|
||||
result = False
|
||||
result = False
|
||||
else:
|
||||
if judge.is_blocked:
|
||||
result = False
|
||||
else:
|
||||
result = hmac.compare_digest(judge.auth_key, key)
|
||||
result = hmac.compare_digest(judge.auth_key, key)
|
||||
|
||||
if not result:
|
||||
json_log.warning(
|
||||
|
@ -140,52 +138,11 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
)
|
||||
return result
|
||||
|
||||
def _update_supported_problems(self, problem_packet):
|
||||
# problem_packet is a dict {code: mtimes} from judge-server
|
||||
self.problems = set(p for p, _ in problem_packet)
|
||||
|
||||
def _update_judge_problems(self):
|
||||
chunk_size = 500
|
||||
|
||||
target_problem_codes = self.problems
|
||||
current_problems = _get_judge_problems(self.judge)
|
||||
|
||||
updated = False
|
||||
problems_to_add = list(target_problem_codes - current_problems)
|
||||
problems_to_remove = list(current_problems - target_problem_codes)
|
||||
|
||||
if problems_to_add:
|
||||
for i in range(0, len(problems_to_add), chunk_size):
|
||||
chunk = problems_to_add[i : i + chunk_size]
|
||||
problem_ids = Problem.objects.filter(code__in=chunk).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
if not problem_ids:
|
||||
continue
|
||||
logger.info("%s: Add %d problems", self.name, len(problem_ids))
|
||||
self.judge.problems.add(*problem_ids)
|
||||
updated = True
|
||||
|
||||
if problems_to_remove:
|
||||
for i in range(0, len(problems_to_remove), chunk_size):
|
||||
chunk = problems_to_remove[i : i + chunk_size]
|
||||
problem_ids = Problem.objects.filter(code__in=chunk).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
if not problem_ids:
|
||||
continue
|
||||
logger.info("%s: Remove %d problems", self.name, len(problem_ids))
|
||||
self.judge.problems.remove(*problem_ids)
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
_get_judge_problems.dirty(self.judge)
|
||||
|
||||
def _connected(self):
|
||||
judge = self.judge = Judge.objects.get(name=self.name)
|
||||
judge.start_time = timezone.now()
|
||||
judge.online = True
|
||||
self._update_judge_problems()
|
||||
judge.problems.set(Problem.objects.filter(code__in=list(self.problems.keys())))
|
||||
judge.runtimes.set(Language.objects.filter(key__in=list(self.executors.keys())))
|
||||
|
||||
# Delete now in case we somehow crashed and left some over from the last connection
|
||||
|
@ -220,8 +177,6 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
def _disconnected(self):
|
||||
Judge.objects.filter(id=self.judge.id).update(online=False)
|
||||
RuntimeVersion.objects.filter(judge=self.judge).delete()
|
||||
self.judge.problems.clear()
|
||||
_get_judge_problems.dirty(self.judge)
|
||||
|
||||
def _update_ping(self):
|
||||
try:
|
||||
|
@ -252,7 +207,8 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
return
|
||||
|
||||
self.timeout = 60
|
||||
self._update_supported_problems(packet["problems"])
|
||||
self._problems = packet["problems"]
|
||||
self.problems = dict(self._problems)
|
||||
self.executors = packet["executors"]
|
||||
self.name = packet["id"]
|
||||
|
||||
|
@ -353,15 +309,7 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
|
||||
def submit(self, id, problem, language, source):
|
||||
data = self.get_related_submission_data(id)
|
||||
if not data:
|
||||
self._update_internal_error_submission(id, "Submission vanished")
|
||||
raise VanishedSubmission()
|
||||
self._working = id
|
||||
self._working_data = {
|
||||
"problem": problem,
|
||||
"language": language,
|
||||
"source": source,
|
||||
}
|
||||
self._no_response_job = threading.Timer(20, self._kill_if_no_response)
|
||||
self.send(
|
||||
{
|
||||
|
@ -480,12 +428,14 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
|
||||
def on_supported_problems(self, packet):
|
||||
logger.info("%s: Updated problem list", self.name)
|
||||
self._update_supported_problems(packet["problems"])
|
||||
|
||||
self._problems = packet["problems"]
|
||||
self.problems = dict(self._problems)
|
||||
if not self.working:
|
||||
self.judges.update_problems(self)
|
||||
|
||||
self._update_judge_problems()
|
||||
self.judge.problems.set(
|
||||
Problem.objects.filter(code__in=list(self.problems.keys()))
|
||||
)
|
||||
json_log.info(
|
||||
self._make_json_log(action="update-problems", count=len(self.problems))
|
||||
)
|
||||
|
@ -550,8 +500,8 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
total += case.total
|
||||
else:
|
||||
if case.batch in batches:
|
||||
batches[case.batch][0] += case.points
|
||||
batches[case.batch][1] += case.total
|
||||
batches[case.batch][0] = min(batches[case.batch][0], case.points)
|
||||
batches[case.batch][1] = max(batches[case.batch][1], case.total)
|
||||
else:
|
||||
batches[case.batch] = [case.points, case.total]
|
||||
memory = max(memory, case.memory)
|
||||
|
@ -563,8 +513,8 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
points += batches[i][0]
|
||||
total += batches[i][1]
|
||||
|
||||
points = points
|
||||
total = total
|
||||
points = round(points, 1)
|
||||
total = round(total, 1)
|
||||
submission.case_points = points
|
||||
submission.case_total = total
|
||||
|
||||
|
@ -702,11 +652,8 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
self._free_self(packet)
|
||||
|
||||
id = packet["submission-id"]
|
||||
self._update_internal_error_submission(id, packet["message"])
|
||||
|
||||
def _update_internal_error_submission(self, id, message):
|
||||
if Submission.objects.filter(id=id).update(
|
||||
status="IE", result="IE", error=message
|
||||
status="IE", result="IE", error=packet["message"]
|
||||
):
|
||||
event.post(
|
||||
"sub_%s" % Submission.get_id_secret(id), {"type": "internal-error"}
|
||||
|
@ -714,9 +661,9 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
self._post_update_submission(id, "internal-error", done=True)
|
||||
json_log.info(
|
||||
self._make_json_log(
|
||||
sub=id,
|
||||
packet,
|
||||
action="internal-error",
|
||||
message=message,
|
||||
message=packet["message"],
|
||||
finish=True,
|
||||
result="IE",
|
||||
)
|
||||
|
@ -725,10 +672,10 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
logger.warning("Unknown submission: %s", id)
|
||||
json_log.error(
|
||||
self._make_json_log(
|
||||
sub=id,
|
||||
packet,
|
||||
action="internal-error",
|
||||
info="unknown submission",
|
||||
message=message,
|
||||
message=packet["message"],
|
||||
finish=True,
|
||||
result="IE",
|
||||
)
|
||||
|
@ -956,11 +903,3 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
"language": data["language__key"],
|
||||
},
|
||||
)
|
||||
|
||||
def on_cleanup(self):
|
||||
db.connection.close()
|
||||
|
||||
|
||||
@cache_wrapper(prefix="gjp", timeout=3600)
|
||||
def _get_judge_problems(judge):
|
||||
return set(judge.problems.values_list("code", flat=True))
|
||||
|
|
|
@ -3,8 +3,6 @@ from collections import namedtuple
|
|||
from operator import attrgetter
|
||||
from threading import RLock
|
||||
|
||||
from judge.bridge.utils import VanishedSubmission
|
||||
|
||||
try:
|
||||
from llist import dllist
|
||||
except ImportError:
|
||||
|
@ -41,8 +39,6 @@ class JudgeList(object):
|
|||
)
|
||||
try:
|
||||
judge.submit(id, problem, language, source)
|
||||
except VanishedSubmission:
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Failed to dispatch %d (%s, %s) to %s",
|
||||
|
@ -93,7 +89,6 @@ class JudgeList(object):
|
|||
logger.info("Judge available after grading %d: %s", submission, judge.name)
|
||||
del self.submission_map[submission]
|
||||
judge._working = False
|
||||
judge._working_data = {}
|
||||
self._handle_free_judge(judge)
|
||||
|
||||
def abort(self, submission):
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
class VanishedSubmission(Exception):
|
||||
pass
|
123
judge/caching.py
123
judge/caching.py
|
@ -1,117 +1,10 @@
|
|||
from inspect import signature
|
||||
from django.core.cache import cache, caches
|
||||
from django.db.models.query import QuerySet
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
|
||||
import hashlib
|
||||
|
||||
from judge.logging import log_debug
|
||||
|
||||
MAX_NUM_CHAR = 50
|
||||
NONE_RESULT = "__None__"
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
def arg_to_str(arg):
|
||||
if hasattr(arg, "id"):
|
||||
return str(arg.id)
|
||||
if isinstance(arg, list) or isinstance(arg, QuerySet):
|
||||
return hashlib.sha1(str(list(arg)).encode()).hexdigest()[:MAX_NUM_CHAR]
|
||||
if len(str(arg)) > MAX_NUM_CHAR:
|
||||
return str(arg)[:MAX_NUM_CHAR]
|
||||
return str(arg)
|
||||
|
||||
|
||||
def filter_args(args_list):
|
||||
return [x for x in args_list if not isinstance(x, WSGIRequest)]
|
||||
|
||||
|
||||
l0_cache = caches["l0"] if "l0" in caches else None
|
||||
|
||||
|
||||
def cache_wrapper(prefix, timeout=None, expected_type=None):
|
||||
def get_key(func, *args, **kwargs):
|
||||
args_list = list(args)
|
||||
signature_args = list(signature(func).parameters.keys())
|
||||
args_list += [kwargs.get(k) for k in signature_args[len(args) :]]
|
||||
args_list = filter_args(args_list)
|
||||
args_list = [arg_to_str(i) for i in args_list]
|
||||
key = prefix + ":" + ":".join(args_list)
|
||||
key = key.replace(" ", "_")
|
||||
return key
|
||||
|
||||
def _get(key):
|
||||
if not l0_cache:
|
||||
return cache.get(key)
|
||||
result = l0_cache.get(key)
|
||||
if result is None:
|
||||
result = cache.get(key)
|
||||
return result
|
||||
|
||||
def _set_l0(key, value):
|
||||
if l0_cache:
|
||||
l0_cache.set(key, value, 30)
|
||||
|
||||
def _set(key, value, timeout):
|
||||
_set_l0(key, value)
|
||||
cache.set(key, value, timeout)
|
||||
|
||||
def decorator(func):
|
||||
def _validate_type(cache_key, result):
|
||||
if expected_type and not isinstance(result, expected_type):
|
||||
data = {
|
||||
"function": f"{func.__module__}.{func.__qualname__}",
|
||||
"result": str(result)[:30],
|
||||
"expected_type": expected_type,
|
||||
"type": type(result),
|
||||
"key": cache_key,
|
||||
}
|
||||
log_debug("invalid_key", data)
|
||||
return False
|
||||
return True
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
cache_key = get_key(func, *args, **kwargs)
|
||||
result = _get(cache_key)
|
||||
if result is not None and _validate_type(cache_key, result):
|
||||
_set_l0(cache_key, result)
|
||||
if type(result) == str and result == NONE_RESULT:
|
||||
result = None
|
||||
return result
|
||||
result = func(*args, **kwargs)
|
||||
if result is None:
|
||||
cache_result = NONE_RESULT
|
||||
else:
|
||||
cache_result = result
|
||||
_set(cache_key, cache_result, timeout)
|
||||
return result
|
||||
|
||||
def dirty(*args, **kwargs):
|
||||
cache_key = get_key(func, *args, **kwargs)
|
||||
cache.delete(cache_key)
|
||||
if l0_cache:
|
||||
l0_cache.delete(cache_key)
|
||||
|
||||
def prefetch_multi(args_list):
|
||||
keys = []
|
||||
for args in args_list:
|
||||
keys.append(get_key(func, *args))
|
||||
results = cache.get_many(keys)
|
||||
for key, result in results.items():
|
||||
if result is not None:
|
||||
_set_l0(key, result)
|
||||
|
||||
def dirty_multi(args_list):
|
||||
keys = []
|
||||
for args in args_list:
|
||||
keys.append(get_key(func, *args))
|
||||
cache.delete_many(keys)
|
||||
if l0_cache:
|
||||
l0_cache.delete_many(keys)
|
||||
|
||||
wrapper.dirty = dirty
|
||||
wrapper.prefetch_multi = prefetch_multi
|
||||
wrapper.dirty_multi = dirty_multi
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
def finished_submission(sub):
|
||||
keys = ["user_complete:%d" % sub.user_id, "user_attempted:%s" % sub.user_id]
|
||||
if hasattr(sub, "contest"):
|
||||
participation = sub.contest.participation
|
||||
keys += ["contest_complete:%d" % participation.id]
|
||||
keys += ["contest_attempted:%d" % participation.id]
|
||||
cache.delete_many(keys)
|
||||
|
|
196
judge/comments.py
Normal file
196
judge/comments.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Count
|
||||
from django.db.models.expressions import F, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.forms import ModelForm
|
||||
from django.http import (
|
||||
HttpResponseForbidden,
|
||||
HttpResponseNotFound,
|
||||
HttpResponseRedirect,
|
||||
)
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import View
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from reversion import revisions
|
||||
from reversion.models import Revision, Version
|
||||
|
||||
from judge.dblock import LockModel
|
||||
from judge.models import Comment, CommentLock, CommentVote, Notification
|
||||
from judge.utils.raw_sql import RawSQLColumn, unique_together_left_join
|
||||
from judge.widgets import HeavyPreviewPageDownWidget
|
||||
from judge.jinja2.reference import get_user_from_text
|
||||
|
||||
|
||||
def add_mention_notifications(comment):
|
||||
user_referred = get_user_from_text(comment.body).exclude(id=comment.author.id)
|
||||
for user in user_referred:
|
||||
notification_ref = Notification(owner=user, comment=comment, category="Mention")
|
||||
notification_ref.save()
|
||||
|
||||
|
||||
def del_mention_notifications(comment):
|
||||
query = {"comment": comment, "category": "Mention"}
|
||||
Notification.objects.filter(**query).delete()
|
||||
|
||||
|
||||
class CommentForm(ModelForm):
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ["body", "parent"]
|
||||
widgets = {
|
||||
"parent": forms.HiddenInput(),
|
||||
}
|
||||
|
||||
if HeavyPreviewPageDownWidget is not None:
|
||||
widgets["body"] = HeavyPreviewPageDownWidget(
|
||||
preview=reverse_lazy("comment_preview"),
|
||||
preview_timeout=1000,
|
||||
hide_preview_button=True,
|
||||
)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
super(CommentForm, self).__init__(*args, **kwargs)
|
||||
self.fields["body"].widget.attrs.update({"placeholder": _("Comment body")})
|
||||
|
||||
def clean(self):
|
||||
if self.request is not None and self.request.user.is_authenticated:
|
||||
profile = self.request.profile
|
||||
if profile.mute:
|
||||
raise ValidationError(_("Your part is silent, little toad."))
|
||||
elif (
|
||||
not self.request.user.is_staff
|
||||
and not profile.submission_set.filter(
|
||||
points=F("problem__points")
|
||||
).exists()
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"You need to have solved at least one problem "
|
||||
"before your voice can be heard."
|
||||
)
|
||||
)
|
||||
return super(CommentForm, self).clean()
|
||||
|
||||
|
||||
class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||
comment_page = None
|
||||
|
||||
def get_comment_page(self):
|
||||
if self.comment_page is None:
|
||||
raise NotImplementedError()
|
||||
return self.comment_page
|
||||
|
||||
def is_comment_locked(self):
|
||||
if self.request.user.has_perm("judge.override_comment_lock"):
|
||||
return False
|
||||
return CommentLock.objects.filter(page=self.get_comment_page()).exists() or (
|
||||
self.request.in_contest
|
||||
and self.request.participation.contest.use_clarifications
|
||||
)
|
||||
|
||||
@method_decorator(login_required)
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
page = self.get_comment_page()
|
||||
|
||||
if self.is_comment_locked():
|
||||
return HttpResponseForbidden()
|
||||
|
||||
parent = request.POST.get("parent")
|
||||
if parent:
|
||||
try:
|
||||
parent = int(parent)
|
||||
except ValueError:
|
||||
return HttpResponseNotFound()
|
||||
else:
|
||||
if not Comment.objects.filter(
|
||||
hidden=False, id=parent, page=page
|
||||
).exists():
|
||||
return HttpResponseNotFound()
|
||||
|
||||
form = CommentForm(request, request.POST)
|
||||
if form.is_valid():
|
||||
comment = form.save(commit=False)
|
||||
comment.author = request.profile
|
||||
comment.page = page
|
||||
|
||||
with LockModel(
|
||||
write=(Comment, Revision, Version), read=(ContentType,)
|
||||
), revisions.create_revision():
|
||||
revisions.set_user(request.user)
|
||||
revisions.set_comment(_("Posted comment"))
|
||||
comment.save()
|
||||
|
||||
# add notification for reply
|
||||
if comment.parent and comment.parent.author != comment.author:
|
||||
notification_reply = Notification(
|
||||
owner=comment.parent.author, comment=comment, category="Reply"
|
||||
)
|
||||
notification_reply.save()
|
||||
|
||||
# add notification for page authors
|
||||
page_authors = comment.page_object.authors.all()
|
||||
for user in page_authors:
|
||||
if user == comment.author:
|
||||
continue
|
||||
notification = Notification(
|
||||
owner=user, comment=comment, category="Comment"
|
||||
)
|
||||
notification.save()
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
add_mention_notifications(comment)
|
||||
|
||||
return HttpResponseRedirect(request.path)
|
||||
|
||||
context = self.get_context_data(object=self.object, comment_form=form)
|
||||
return self.render_to_response(context)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return self.render_to_response(
|
||||
self.get_context_data(
|
||||
object=self.object,
|
||||
comment_form=CommentForm(
|
||||
request, initial={"page": self.get_comment_page(), "parent": None}
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CommentedDetailView, self).get_context_data(**kwargs)
|
||||
queryset = Comment.objects.filter(hidden=False, page=self.get_comment_page())
|
||||
context["has_comments"] = queryset.exists()
|
||||
context["comment_lock"] = self.is_comment_locked()
|
||||
queryset = (
|
||||
queryset.select_related("author__user")
|
||||
.defer("author__about")
|
||||
.annotate(revisions=Count("versions"))
|
||||
)
|
||||
|
||||
if self.request.user.is_authenticated:
|
||||
queryset = queryset.annotate(
|
||||
vote_score=Coalesce(RawSQLColumn(CommentVote, "score"), Value(0))
|
||||
)
|
||||
profile = self.request.profile
|
||||
unique_together_left_join(
|
||||
queryset, CommentVote, "comment", "voter", profile.id
|
||||
)
|
||||
context["is_new_user"] = (
|
||||
not self.request.user.is_staff
|
||||
and not profile.submission_set.filter(
|
||||
points=F("problem__points")
|
||||
).exists()
|
||||
)
|
||||
context["comment_list"] = queryset
|
||||
context["vote_hide_threshold"] = settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD
|
||||
return context
|
|
@ -3,6 +3,4 @@ from judge.contest_format.default import DefaultContestFormat
|
|||
from judge.contest_format.ecoo import ECOOContestFormat
|
||||
from judge.contest_format.icpc import ICPCContestFormat
|
||||
from judge.contest_format.ioi import IOIContestFormat
|
||||
from judge.contest_format.new_ioi import NewIOIContestFormat
|
||||
from judge.contest_format.ultimate import UltimateContestFormat
|
||||
from judge.contest_format.registry import choices, formats
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy
|
|||
|
||||
from judge.contest_format.default import DefaultContestFormat
|
||||
from judge.contest_format.registry import register_contest_format
|
||||
from judge.timezone import from_database_time, to_database_time
|
||||
from judge.timezone import from_database_time
|
||||
from judge.utils.timedelta import nice_repr
|
||||
|
||||
|
||||
|
@ -54,10 +54,6 @@ class AtCoderContestFormat(DefaultContestFormat):
|
|||
points = 0
|
||||
format_data = {}
|
||||
|
||||
frozen_time = self.contest.end_time
|
||||
if self.contest.freeze_after:
|
||||
frozen_time = participation.start + self.contest.freeze_after
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
|
@ -70,10 +66,9 @@ class AtCoderContestFormat(DefaultContestFormat):
|
|||
FROM judge_contestproblem cp INNER JOIN
|
||||
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
|
||||
judge_submission sub ON (sub.id = cs.submission_id)
|
||||
WHERE sub.date < %s
|
||||
GROUP BY cp.id
|
||||
""",
|
||||
(participation.id, participation.id, to_database_time(frozen_time)),
|
||||
(participation.id, participation.id),
|
||||
)
|
||||
|
||||
for score, time, prob in cursor.fetchall():
|
||||
|
@ -105,14 +100,13 @@ class AtCoderContestFormat(DefaultContestFormat):
|
|||
format_data[str(prob)] = {"time": dt, "points": score, "penalty": prev}
|
||||
points += score
|
||||
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = cumtime + penalty
|
||||
participation.score = round(points, self.contest.points_precision)
|
||||
participation.score = points
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
penalty = (
|
||||
|
@ -120,11 +114,11 @@ class AtCoderContestFormat(DefaultContestFormat):
|
|||
'<small style="color:red"> ({penalty})</small>',
|
||||
penalty=floatformat(format_data["penalty"]),
|
||||
)
|
||||
if format_data.get("penalty")
|
||||
if format_data["penalty"]
|
||||
else ""
|
||||
)
|
||||
return format_html(
|
||||
'<td class="{state} problem-score-col"><a data-featherlight="{url}" href="#">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
|
||||
'<td class="{state} problem-score-col"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
|
||||
state=(
|
||||
(
|
||||
"pretest-"
|
||||
|
@ -135,13 +129,12 @@ class AtCoderContestFormat(DefaultContestFormat):
|
|||
+ self.best_solution_state(
|
||||
format_data["points"], contest_problem.points
|
||||
)
|
||||
+ (" frozen" if format_data.get("frozen") else "")
|
||||
),
|
||||
url=reverse(
|
||||
"contest_user_submissions_ajax",
|
||||
"contest_user_submissions",
|
||||
args=[
|
||||
self.contest.key,
|
||||
participation.id,
|
||||
participation.user.user.username,
|
||||
contest_problem.problem.code,
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
from django.db.models import Max
|
||||
|
||||
from django.utils import six
|
||||
|
||||
|
||||
class abstractclassmethod(classmethod):
|
||||
|
@ -10,9 +11,7 @@ class abstractclassmethod(classmethod):
|
|||
super(abstractclassmethod, self).__init__(callable)
|
||||
|
||||
|
||||
class BaseContestFormat(metaclass=ABCMeta):
|
||||
has_hidden_subtasks = False
|
||||
|
||||
class BaseContestFormat(six.with_metaclass(ABCMeta)):
|
||||
@abstractmethod
|
||||
def __init__(self, contest, config):
|
||||
self.config = config
|
||||
|
@ -50,7 +49,7 @@ class BaseContestFormat(metaclass=ABCMeta):
|
|||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def display_user_problem(self, participation, contest_problem, show_final):
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
"""
|
||||
Returns the HTML fragment to show a user's performance on an individual problem. This is expected to use
|
||||
information from the format_data field instead of computing it from scratch.
|
||||
|
@ -62,7 +61,7 @@ class BaseContestFormat(metaclass=ABCMeta):
|
|||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def display_participation_result(self, participation, show_final):
|
||||
def display_participation_result(self, participation):
|
||||
"""
|
||||
Returns the HTML fragment to show a user's performance on the whole contest. This is expected to use
|
||||
information from the format_data field instead of computing it from scratch.
|
||||
|
@ -98,26 +97,3 @@ class BaseContestFormat(metaclass=ABCMeta):
|
|||
if points == total:
|
||||
return "full-score"
|
||||
return "partial-score"
|
||||
|
||||
def handle_frozen_state(self, participation, format_data):
|
||||
hidden_subtasks = {}
|
||||
if hasattr(self, "get_hidden_subtasks"):
|
||||
hidden_subtasks = self.get_hidden_subtasks()
|
||||
|
||||
queryset = participation.submissions.values("problem_id").annotate(
|
||||
time=Max("submission__date")
|
||||
)
|
||||
for result in queryset:
|
||||
problem = str(result["problem_id"])
|
||||
if not (self.contest.freeze_after or hidden_subtasks.get(problem)):
|
||||
continue
|
||||
if format_data.get(problem):
|
||||
is_after_freeze = (
|
||||
self.contest.freeze_after
|
||||
and result["time"]
|
||||
>= self.contest.freeze_after + participation.start
|
||||
)
|
||||
if is_after_freeze or hidden_subtasks.get(problem):
|
||||
format_data[problem]["frozen"] = True
|
||||
else:
|
||||
format_data[problem] = {"time": 0, "points": 0, "frozen": True}
|
||||
|
|
|
@ -32,19 +32,10 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
points = 0
|
||||
format_data = {}
|
||||
|
||||
queryset = participation.submissions
|
||||
|
||||
if self.contest.freeze_after:
|
||||
queryset = queryset.filter(
|
||||
submission__date__lt=participation.start + self.contest.freeze_after
|
||||
)
|
||||
|
||||
queryset = queryset.values("problem_id").annotate(
|
||||
for result in participation.submissions.values("problem_id").annotate(
|
||||
time=Max("submission__date"),
|
||||
points=Max("points"),
|
||||
)
|
||||
|
||||
for result in queryset:
|
||||
):
|
||||
dt = (result["time"] - participation.start).total_seconds()
|
||||
if result["points"]:
|
||||
cumtime += dt
|
||||
|
@ -54,14 +45,13 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
}
|
||||
points += result["points"]
|
||||
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = max(cumtime, 0)
|
||||
participation.score = round(points, self.contest.points_precision)
|
||||
participation.score = points
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
return format_html(
|
||||
|
@ -76,13 +66,12 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
+ self.best_solution_state(
|
||||
format_data["points"], contest_problem.points
|
||||
)
|
||||
+ (" frozen" if format_data.get("frozen") else "")
|
||||
),
|
||||
url=reverse(
|
||||
"contest_user_submissions_ajax",
|
||||
args=[
|
||||
self.contest.key,
|
||||
participation.id,
|
||||
participation.user.user.username,
|
||||
contest_problem.problem.code,
|
||||
],
|
||||
),
|
||||
|
@ -94,7 +83,7 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
else:
|
||||
return mark_safe('<td class="problem-score-col"></td>')
|
||||
|
||||
def display_participation_result(self, participation, show_final=False):
|
||||
def display_participation_result(self, participation):
|
||||
return format_html(
|
||||
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
|
||||
points=floatformat(participation.score, -self.contest.points_precision),
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy
|
|||
|
||||
from judge.contest_format.default import DefaultContestFormat
|
||||
from judge.contest_format.registry import register_contest_format
|
||||
from judge.timezone import from_database_time, to_database_time
|
||||
from judge.timezone import from_database_time
|
||||
from judge.utils.timedelta import nice_repr
|
||||
|
||||
|
||||
|
@ -60,10 +60,6 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
points = 0
|
||||
format_data = {}
|
||||
|
||||
frozen_time = self.contest.end_time
|
||||
if self.contest.freeze_after:
|
||||
frozen_time = participation.start + self.contest.freeze_after
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
|
@ -81,15 +77,9 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
FROM judge_contestproblem cp INNER JOIN
|
||||
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
|
||||
judge_submission sub ON (sub.id = cs.submission_id)
|
||||
WHERE sub.date < %s
|
||||
GROUP BY cp.id
|
||||
""",
|
||||
(
|
||||
participation.id,
|
||||
participation.id,
|
||||
participation.id,
|
||||
to_database_time(frozen_time),
|
||||
),
|
||||
(participation.id, participation.id, participation.id),
|
||||
)
|
||||
|
||||
for score, time, prob, subs, max_score in cursor.fetchall():
|
||||
|
@ -115,26 +105,25 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
format_data[str(prob)] = {"time": dt, "points": score, "bonus": bonus}
|
||||
points += score
|
||||
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = cumtime
|
||||
participation.score = round(points, self.contest.points_precision)
|
||||
participation.score = points
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
bonus = (
|
||||
format_html(
|
||||
"<small> +{bonus}</small>", bonus=floatformat(format_data["bonus"])
|
||||
)
|
||||
if format_data.get("bonus")
|
||||
if format_data["bonus"]
|
||||
else ""
|
||||
)
|
||||
|
||||
return format_html(
|
||||
'<td class="{state}"><a data-featherlight="{url}" href="#">{points}{bonus}<div class="solving-time">{time}</div></a></td>',
|
||||
'<td class="{state}"><a href="{url}">{points}{bonus}<div class="solving-time">{time}</div></a></td>',
|
||||
state=(
|
||||
(
|
||||
"pretest-"
|
||||
|
@ -145,13 +134,12 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
+ self.best_solution_state(
|
||||
format_data["points"], contest_problem.points
|
||||
)
|
||||
+ (" frozen" if format_data.get("frozen") else "")
|
||||
),
|
||||
url=reverse(
|
||||
"contest_user_submissions_ajax",
|
||||
"contest_user_submissions",
|
||||
args=[
|
||||
self.contest.key,
|
||||
participation.id,
|
||||
participation.user.user.username,
|
||||
contest_problem.problem.code,
|
||||
],
|
||||
),
|
||||
|
@ -162,7 +150,7 @@ class ECOOContestFormat(DefaultContestFormat):
|
|||
else:
|
||||
return mark_safe("<td></td>")
|
||||
|
||||
def display_participation_result(self, participation, show_final=False):
|
||||
def display_participation_result(self, participation):
|
||||
return format_html(
|
||||
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
|
||||
points=floatformat(participation.score),
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy
|
|||
|
||||
from judge.contest_format.default import DefaultContestFormat
|
||||
from judge.contest_format.registry import register_contest_format
|
||||
from judge.timezone import from_database_time, to_database_time
|
||||
from judge.timezone import from_database_time
|
||||
from judge.utils.timedelta import nice_repr
|
||||
|
||||
|
||||
|
@ -55,10 +55,6 @@ class ICPCContestFormat(DefaultContestFormat):
|
|||
score = 0
|
||||
format_data = {}
|
||||
|
||||
frozen_time = self.contest.end_time
|
||||
if self.contest.freeze_after:
|
||||
frozen_time = participation.start + self.contest.freeze_after
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
|
@ -71,10 +67,9 @@ class ICPCContestFormat(DefaultContestFormat):
|
|||
FROM judge_contestproblem cp INNER JOIN
|
||||
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
|
||||
judge_submission sub ON (sub.id = cs.submission_id)
|
||||
WHERE sub.date < %s
|
||||
GROUP BY cp.id
|
||||
""",
|
||||
(participation.id, participation.id, to_database_time(frozen_time)),
|
||||
(participation.id, participation.id),
|
||||
)
|
||||
|
||||
for points, time, prob in cursor.fetchall():
|
||||
|
@ -107,26 +102,25 @@ class ICPCContestFormat(DefaultContestFormat):
|
|||
format_data[str(prob)] = {"time": dt, "points": points, "penalty": prev}
|
||||
score += points
|
||||
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = max(0, cumtime + penalty)
|
||||
participation.score = round(score, self.contest.points_precision)
|
||||
participation.score = score
|
||||
participation.tiebreaker = last # field is sorted from least to greatest
|
||||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
penalty = (
|
||||
format_html(
|
||||
'<small style="color:red"> +{penalty}</small>',
|
||||
'<small style="color:red"> ({penalty})</small>',
|
||||
penalty=floatformat(format_data["penalty"]),
|
||||
)
|
||||
if format_data.get("penalty")
|
||||
if format_data["penalty"]
|
||||
else ""
|
||||
)
|
||||
return format_html(
|
||||
'<td class="{state}"><a data-featherlight="{url}" href="#">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
|
||||
'<td class="{state}"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
|
||||
state=(
|
||||
(
|
||||
"pretest-"
|
||||
|
@ -137,13 +131,12 @@ class ICPCContestFormat(DefaultContestFormat):
|
|||
+ self.best_solution_state(
|
||||
format_data["points"], contest_problem.points
|
||||
)
|
||||
+ (" frozen" if format_data.get("frozen") else "")
|
||||
),
|
||||
url=reverse(
|
||||
"contest_user_submissions_ajax",
|
||||
"contest_user_submissions",
|
||||
args=[
|
||||
self.contest.key,
|
||||
participation.id,
|
||||
participation.user.user.username,
|
||||
contest_problem.problem.code,
|
||||
],
|
||||
),
|
||||
|
|
|
@ -12,7 +12,6 @@ from judge.contest_format.default import DefaultContestFormat
|
|||
from judge.contest_format.registry import register_contest_format
|
||||
from judge.timezone import from_database_time
|
||||
from judge.utils.timedelta import nice_repr
|
||||
from django.db.models import Min, OuterRef, Subquery
|
||||
|
||||
|
||||
@register_contest_format("ioi")
|
||||
|
@ -46,56 +45,50 @@ class IOIContestFormat(DefaultContestFormat):
|
|||
|
||||
def update_participation(self, participation):
|
||||
cumtime = 0
|
||||
score = 0
|
||||
points = 0
|
||||
format_data = {}
|
||||
|
||||
queryset = participation.submissions
|
||||
if self.contest.freeze_after:
|
||||
queryset = queryset.filter(
|
||||
submission__date__lt=participation.start + self.contest.freeze_after
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT MAX(cs.points) as `score`, (
|
||||
SELECT MIN(csub.date)
|
||||
FROM judge_contestsubmission ccs LEFT OUTER JOIN
|
||||
judge_submission csub ON (csub.id = ccs.submission_id)
|
||||
WHERE ccs.problem_id = cp.id AND ccs.participation_id = %s AND ccs.points = MAX(cs.points)
|
||||
) AS `time`, cp.id AS `prob`
|
||||
FROM judge_contestproblem cp INNER JOIN
|
||||
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
|
||||
judge_submission sub ON (sub.id = cs.submission_id)
|
||||
GROUP BY cp.id
|
||||
""",
|
||||
(participation.id, participation.id),
|
||||
)
|
||||
|
||||
queryset = (
|
||||
queryset.values("problem_id")
|
||||
.filter(
|
||||
points=Subquery(
|
||||
queryset.filter(problem_id=OuterRef("problem_id"))
|
||||
.order_by("-points")
|
||||
.values("points")[:1]
|
||||
)
|
||||
)
|
||||
.annotate(time=Min("submission__date"))
|
||||
.values_list("problem_id", "time", "points")
|
||||
)
|
||||
for score, time, prob in cursor.fetchall():
|
||||
if self.config["cumtime"]:
|
||||
dt = (
|
||||
from_database_time(time) - participation.start
|
||||
).total_seconds()
|
||||
if score:
|
||||
cumtime += dt
|
||||
else:
|
||||
dt = 0
|
||||
|
||||
for problem_id, time, points in queryset:
|
||||
if self.config["cumtime"]:
|
||||
dt = (time - participation.start).total_seconds()
|
||||
if points:
|
||||
cumtime += dt
|
||||
else:
|
||||
dt = 0
|
||||
format_data[str(prob)] = {"time": dt, "points": score}
|
||||
points += score
|
||||
|
||||
format_data[str(problem_id)] = {"points": points, "time": dt}
|
||||
score += points
|
||||
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = max(cumtime, 0)
|
||||
participation.score = round(score, self.contest.points_precision)
|
||||
participation.score = points
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
participation.save()
|
||||
|
||||
def display_user_problem(self, participation, contest_problem, show_final=False):
|
||||
if show_final:
|
||||
format_data = (participation.format_data_final or {}).get(
|
||||
str(contest_problem.id)
|
||||
)
|
||||
else:
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
def display_user_problem(self, participation, contest_problem):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
return format_html(
|
||||
'<td class="{state} problem-score-col"><a data-featherlight="{url}" href="#">{points}<div class="solving-time">{time}</div></a></td>',
|
||||
'<td class="{state} problem-score-col"><a href="{url}">{points}<div class="solving-time">{time}</div></a></td>',
|
||||
state=(
|
||||
(
|
||||
"pretest-"
|
||||
|
@ -106,19 +99,16 @@ class IOIContestFormat(DefaultContestFormat):
|
|||
+ self.best_solution_state(
|
||||
format_data["points"], contest_problem.points
|
||||
)
|
||||
+ (" frozen" if format_data.get("frozen") else "")
|
||||
),
|
||||
url=reverse(
|
||||
"contest_user_submissions_ajax",
|
||||
"contest_user_submissions",
|
||||
args=[
|
||||
self.contest.key,
|
||||
participation.id,
|
||||
participation.user.user.username,
|
||||
contest_problem.problem.code,
|
||||
],
|
||||
),
|
||||
points=floatformat(
|
||||
format_data["points"], -self.contest.points_precision
|
||||
),
|
||||
points=floatformat(format_data["points"]),
|
||||
time=nice_repr(timedelta(seconds=format_data["time"]), "noday")
|
||||
if self.config["cumtime"]
|
||||
else "",
|
||||
|
@ -126,17 +116,11 @@ class IOIContestFormat(DefaultContestFormat):
|
|||
else:
|
||||
return mark_safe('<td class="problem-score-col"></td>')
|
||||
|
||||
def display_participation_result(self, participation, show_final=False):
|
||||
if show_final:
|
||||
score = participation.score_final
|
||||
cumtime = participation.cumtime_final
|
||||
else:
|
||||
score = participation.score
|
||||
cumtime = participation.cumtime
|
||||
def display_participation_result(self, participation):
|
||||
return format_html(
|
||||
'<td class="user-points">{points}<div class="solving-time">{cumtime}</div></td>',
|
||||
points=floatformat(score, -self.contest.points_precision),
|
||||
cumtime=nice_repr(timedelta(seconds=cumtime), "noday")
|
||||
points=floatformat(participation.score),
|
||||
cumtime=nice_repr(timedelta(seconds=participation.cumtime), "noday")
|
||||
if self.config["cumtime"]
|
||||
else "",
|
||||
)
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
from django.db import connection
|
||||
from django.utils.translation import gettext as _, gettext_lazy
|
||||
|
||||
from judge.contest_format.ioi import IOIContestFormat
|
||||
from judge.contest_format.registry import register_contest_format
|
||||
from judge.timezone import from_database_time, to_database_time
|
||||
|
||||
|
||||
@register_contest_format("ioi16")
|
||||
class NewIOIContestFormat(IOIContestFormat):
|
||||
name = gettext_lazy("New IOI")
|
||||
config_defaults = {"cumtime": False}
|
||||
has_hidden_subtasks = True
|
||||
"""
|
||||
cumtime: Specify True if time penalties are to be computed. Defaults to False.
|
||||
"""
|
||||
|
||||
def get_hidden_subtasks(self):
|
||||
queryset = self.contest.contest_problems.values_list("id", "hidden_subtasks")
|
||||
res = {}
|
||||
for problem_id, hidden_subtasks in queryset:
|
||||
subtasks = set()
|
||||
if hidden_subtasks:
|
||||
hidden_subtasks = hidden_subtasks.split(",")
|
||||
for i in hidden_subtasks:
|
||||
try:
|
||||
subtasks.add(int(i))
|
||||
except Exception as e:
|
||||
pass
|
||||
res[str(problem_id)] = subtasks
|
||||
return res
|
||||
|
||||
def get_results_by_subtask(self, participation, include_frozen=False):
|
||||
frozen_time = self.contest.end_time
|
||||
if self.contest.freeze_after and not include_frozen:
|
||||
frozen_time = participation.start + self.contest.freeze_after
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT q.prob,
|
||||
q.prob_points,
|
||||
MIN(q.date) as `date`,
|
||||
q.batch_points,
|
||||
q.total_batch_points,
|
||||
q.batch,
|
||||
q.subid
|
||||
FROM (
|
||||
SELECT cp.id as `prob`,
|
||||
cp.points as `prob_points`,
|
||||
sub.id as `subid`,
|
||||
sub.date as `date`,
|
||||
tc.points as `points`,
|
||||
tc.batch as `batch`,
|
||||
SUM(tc.points) as `batch_points`,
|
||||
SUM(tc.total) as `total_batch_points`
|
||||
FROM judge_contestproblem cp
|
||||
INNER JOIN
|
||||
judge_contestsubmission cs
|
||||
ON (cs.problem_id = cp.id AND cs.participation_id = %s)
|
||||
LEFT OUTER JOIN
|
||||
judge_submission sub
|
||||
ON (sub.id = cs.submission_id AND sub.status = 'D')
|
||||
INNER JOIN judge_submissiontestcase tc
|
||||
ON sub.id = tc.submission_id
|
||||
WHERE sub.date < %s
|
||||
GROUP BY cp.id, tc.batch, sub.id
|
||||
) q
|
||||
INNER JOIN (
|
||||
SELECT prob, batch, MAX(r.batch_points) as max_batch_points
|
||||
FROM (
|
||||
SELECT cp.id as `prob`,
|
||||
tc.batch as `batch`,
|
||||
SUM(tc.points) as `batch_points`
|
||||
FROM judge_contestproblem cp
|
||||
INNER JOIN
|
||||
judge_contestsubmission cs
|
||||
ON (cs.problem_id = cp.id AND cs.participation_id = %s)
|
||||
LEFT OUTER JOIN
|
||||
judge_submission sub
|
||||
ON (sub.id = cs.submission_id AND sub.status = 'D')
|
||||
INNER JOIN judge_submissiontestcase tc
|
||||
ON sub.id = tc.submission_id
|
||||
WHERE sub.date < %s
|
||||
GROUP BY cp.id, tc.batch, sub.id
|
||||
) r
|
||||
GROUP BY prob, batch
|
||||
) p
|
||||
ON p.prob = q.prob AND (p.batch = q.batch OR p.batch is NULL AND q.batch is NULL)
|
||||
WHERE p.max_batch_points = q.batch_points
|
||||
GROUP BY q.prob, q.batch
|
||||
""",
|
||||
(
|
||||
participation.id,
|
||||
to_database_time(frozen_time),
|
||||
participation.id,
|
||||
to_database_time(frozen_time),
|
||||
),
|
||||
)
|
||||
|
||||
return cursor.fetchall()
|
||||
|
||||
def update_participation(self, participation):
|
||||
hidden_subtasks = self.get_hidden_subtasks()
|
||||
|
||||
def calculate_format_data(participation, include_frozen):
|
||||
format_data = {}
|
||||
for (
|
||||
problem_id,
|
||||
problem_points,
|
||||
time,
|
||||
subtask_points,
|
||||
total_subtask_points,
|
||||
subtask,
|
||||
sub_id,
|
||||
) in self.get_results_by_subtask(participation, include_frozen):
|
||||
problem_id = str(problem_id)
|
||||
time = from_database_time(time)
|
||||
if self.config["cumtime"]:
|
||||
dt = (time - participation.start).total_seconds()
|
||||
else:
|
||||
dt = 0
|
||||
|
||||
if format_data.get(problem_id) is None:
|
||||
format_data[problem_id] = {
|
||||
"points": 0,
|
||||
"time": 0,
|
||||
"total_points": 0,
|
||||
}
|
||||
if (
|
||||
subtask not in hidden_subtasks.get(problem_id, set())
|
||||
or include_frozen
|
||||
):
|
||||
format_data[problem_id]["points"] += subtask_points
|
||||
format_data[problem_id]["total_points"] += total_subtask_points
|
||||
format_data[problem_id]["time"] = max(
|
||||
dt, format_data[problem_id]["time"]
|
||||
)
|
||||
format_data[problem_id]["problem_points"] = problem_points
|
||||
|
||||
return format_data
|
||||
|
||||
def recalculate_results(format_data):
|
||||
cumtime = 0
|
||||
score = 0
|
||||
for problem_data in format_data.values():
|
||||
if not problem_data["total_points"]:
|
||||
continue
|
||||
penalty = problem_data["time"]
|
||||
problem_data["points"] = (
|
||||
problem_data["points"]
|
||||
/ problem_data["total_points"]
|
||||
* problem_data["problem_points"]
|
||||
)
|
||||
if self.config["cumtime"] and problem_data["points"]:
|
||||
cumtime += penalty
|
||||
score += problem_data["points"]
|
||||
return score, cumtime
|
||||
|
||||
format_data = calculate_format_data(participation, False)
|
||||
score, cumtime = recalculate_results(format_data)
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = max(cumtime, 0)
|
||||
participation.score = round(score, self.contest.points_precision)
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
|
||||
format_data_final = calculate_format_data(participation, True)
|
||||
score_final, cumtime_final = recalculate_results(format_data_final)
|
||||
participation.cumtime_final = max(cumtime_final, 0)
|
||||
participation.score_final = round(score_final, self.contest.points_precision)
|
||||
participation.format_data_final = format_data_final
|
||||
participation.save()
|
|
@ -1,3 +1,5 @@
|
|||
from django.utils import six
|
||||
|
||||
formats = {}
|
||||
|
||||
|
||||
|
@ -11,4 +13,4 @@ def register_contest_format(name):
|
|||
|
||||
|
||||
def choices():
|
||||
return [(key, value.name) for key, value in sorted(formats.items())]
|
||||
return [(key, value.name) for key, value in sorted(six.iteritems(formats))]
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from judge.contest_format.ioi import IOIContestFormat
|
||||
from judge.contest_format.registry import register_contest_format
|
||||
from django.db.models import Min, OuterRef, Subquery
|
||||
|
||||
# This contest format only counts last submission for each problem.
|
||||
|
||||
|
||||
@register_contest_format("ultimate")
|
||||
class UltimateContestFormat(IOIContestFormat):
|
||||
name = gettext_lazy("Ultimate")
|
||||
|
||||
def update_participation(self, participation):
|
||||
cumtime = 0
|
||||
score = 0
|
||||
format_data = {}
|
||||
|
||||
queryset = participation.submissions
|
||||
if self.contest.freeze_after:
|
||||
queryset = queryset.filter(
|
||||
submission__date__lt=participation.start + self.contest.freeze_after
|
||||
)
|
||||
|
||||
queryset = (
|
||||
queryset.values("problem_id")
|
||||
.filter(
|
||||
id=Subquery(
|
||||
queryset.filter(problem_id=OuterRef("problem_id"))
|
||||
.order_by("-id")
|
||||
.values("id")[:1]
|
||||
)
|
||||
)
|
||||
.values_list("problem_id", "submission__date", "points")
|
||||
)
|
||||
|
||||
for problem_id, time, points in queryset:
|
||||
if self.config["cumtime"]:
|
||||
dt = (time - participation.start).total_seconds()
|
||||
if points:
|
||||
cumtime += dt
|
||||
else:
|
||||
dt = 0
|
||||
format_data[str(problem_id)] = {
|
||||
"time": dt,
|
||||
"points": points,
|
||||
}
|
||||
score += points
|
||||
|
||||
self.handle_frozen_state(participation, format_data)
|
||||
participation.cumtime = max(cumtime, 0)
|
||||
participation.score = round(score, self.contest.points_precision)
|
||||
participation.tiebreaker = 0
|
||||
participation.format_data = format_data
|
||||
participation.save()
|
|
@ -1,22 +0,0 @@
|
|||
from django.utils.translation import gettext_lazy as _, ngettext
|
||||
|
||||
|
||||
def custom_trans():
|
||||
return [
|
||||
# Password reset
|
||||
ngettext(
|
||||
"This password is too short. It must contain at least %(min_length)d character.",
|
||||
"This password is too short. It must contain at least %(min_length)d characters.",
|
||||
0,
|
||||
),
|
||||
ngettext(
|
||||
"Your password must contain at least %(min_length)d character.",
|
||||
"Your password must contain at least %(min_length)d characters.",
|
||||
0,
|
||||
),
|
||||
_("The two password fields didn’t match."),
|
||||
_("Your password can’t be entirely numeric."),
|
||||
# Navbar
|
||||
_("Bug Report"),
|
||||
_("Courses"),
|
||||
]
|
|
@ -16,7 +16,7 @@ class EventPoster(object):
|
|||
|
||||
def _connect(self):
|
||||
self._conn = pika.BlockingConnection(
|
||||
pika.URLParameters(settings.EVENT_DAEMON_AMQP),
|
||||
pika.URLParameters(settings.EVENT_DAEMON_AMQP)
|
||||
)
|
||||
self._chan = self._conn.channel()
|
||||
|
||||
|
@ -25,7 +25,7 @@ class EventPoster(object):
|
|||
id = int(time() * 1000000)
|
||||
self._chan.basic_publish(
|
||||
self._exchange,
|
||||
"#",
|
||||
"",
|
||||
json.dumps({"id": id, "channel": channel, "message": message}),
|
||||
)
|
||||
return id
|
||||
|
|
120
judge/feed.py
Normal file
120
judge/feed.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
|
||||
from judge.jinja2.markdown import markdown
|
||||
from judge.models import BlogPost, Comment, Problem
|
||||
|
||||
import re
|
||||
|
||||
|
||||
# https://lsimons.wordpress.com/2011/03/17/stripping-illegal-characters-out-of-xml-in-python/
|
||||
def escape_xml_illegal_chars(val, replacement="?"):
|
||||
_illegal_xml_chars_RE = re.compile(
|
||||
"[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]"
|
||||
)
|
||||
return _illegal_xml_chars_RE.sub(replacement, val)
|
||||
|
||||
|
||||
class ProblemFeed(Feed):
|
||||
title = "Recently Added %s Problems" % settings.SITE_NAME
|
||||
link = "/"
|
||||
description = (
|
||||
"The latest problems added on the %s website" % settings.SITE_LONG_NAME
|
||||
)
|
||||
|
||||
def items(self):
|
||||
return (
|
||||
Problem.objects.filter(is_public=True, is_organization_private=False)
|
||||
.defer("description")
|
||||
.order_by("-date", "-id")[:25]
|
||||
)
|
||||
|
||||
def item_title(self, problem):
|
||||
return problem.name
|
||||
|
||||
def item_description(self, problem):
|
||||
key = "problem_feed:%d" % problem.id
|
||||
desc = cache.get(key)
|
||||
if desc is None:
|
||||
desc = str(markdown(problem.description, "problem"))[:500] + "..."
|
||||
desc = escape_xml_illegal_chars(desc)
|
||||
cache.set(key, desc, 86400)
|
||||
return desc
|
||||
|
||||
def item_pubdate(self, problem):
|
||||
return problem.date
|
||||
|
||||
item_updateddate = item_pubdate
|
||||
|
||||
|
||||
class AtomProblemFeed(ProblemFeed):
|
||||
feed_type = Atom1Feed
|
||||
subtitle = ProblemFeed.description
|
||||
|
||||
|
||||
class CommentFeed(Feed):
|
||||
title = "Latest %s Comments" % settings.SITE_NAME
|
||||
link = "/"
|
||||
description = "The latest comments on the %s website" % settings.SITE_LONG_NAME
|
||||
|
||||
def items(self):
|
||||
return Comment.most_recent(AnonymousUser(), 25)
|
||||
|
||||
def item_title(self, comment):
|
||||
return "%s -> %s" % (comment.author.user.username, comment.page_title)
|
||||
|
||||
def item_description(self, comment):
|
||||
key = "comment_feed:%d" % comment.id
|
||||
desc = cache.get(key)
|
||||
if desc is None:
|
||||
desc = str(markdown(comment.body, "comment"))
|
||||
desc = escape_xml_illegal_chars(desc)
|
||||
cache.set(key, desc, 86400)
|
||||
return desc
|
||||
|
||||
def item_pubdate(self, comment):
|
||||
return comment.time
|
||||
|
||||
item_updateddate = item_pubdate
|
||||
|
||||
|
||||
class AtomCommentFeed(CommentFeed):
|
||||
feed_type = Atom1Feed
|
||||
subtitle = CommentFeed.description
|
||||
|
||||
|
||||
class BlogFeed(Feed):
|
||||
title = "Latest %s Blog Posts" % settings.SITE_NAME
|
||||
link = "/"
|
||||
description = "The latest blog posts from the %s" % settings.SITE_LONG_NAME
|
||||
|
||||
def items(self):
|
||||
return BlogPost.objects.filter(
|
||||
visible=True, publish_on__lte=timezone.now()
|
||||
).order_by("-sticky", "-publish_on")
|
||||
|
||||
def item_title(self, post):
|
||||
return post.title
|
||||
|
||||
def item_description(self, post):
|
||||
key = "blog_feed:%d" % post.id
|
||||
summary = cache.get(key)
|
||||
if summary is None:
|
||||
summary = str(markdown(post.summary or post.content, "blog"))
|
||||
summary = escape_xml_illegal_chars(summary)
|
||||
cache.set(key, summary, 86400)
|
||||
return summary
|
||||
|
||||
def item_pubdate(self, post):
|
||||
return post.publish_on
|
||||
|
||||
item_updateddate = item_pubdate
|
||||
|
||||
|
||||
class AtomBlogFeed(BlogFeed):
|
||||
feed_type = Atom1Feed
|
||||
subtitle = BlogFeed.description
|
|
@ -8,6 +8,7 @@
|
|||
"ip": "10.0.2.2",
|
||||
"language": 1,
|
||||
"last_access": "2017-12-02T08:57:10.093Z",
|
||||
"math_engine": "auto",
|
||||
"mute": false,
|
||||
"organizations": [
|
||||
1
|
||||
|
@ -17,7 +18,8 @@
|
|||
"problem_count": 0,
|
||||
"rating": null,
|
||||
"timezone": "America/Toronto",
|
||||
"user": 1
|
||||
"user": 1,
|
||||
"user_script": ""
|
||||
},
|
||||
"model": "judge.profile",
|
||||
"pk": 1
|
||||
|
@ -145,8 +147,25 @@
|
|||
},
|
||||
{
|
||||
"fields": {
|
||||
"domain": "localhost:8000",
|
||||
"name": "LQDOJ"
|
||||
"author": 1,
|
||||
"body": "This is your first comment!",
|
||||
"hidden": false,
|
||||
"level": 0,
|
||||
"lft": 1,
|
||||
"page": "b:1",
|
||||
"parent": null,
|
||||
"rght": 2,
|
||||
"score": 0,
|
||||
"time": "2017-12-02T08:46:54.007Z",
|
||||
"tree_id": 1
|
||||
},
|
||||
"model": "judge.comment",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"domain": "localhost:8081",
|
||||
"name": "DMOJ: Modern Online Judge"
|
||||
},
|
||||
"model": "sites.site",
|
||||
"pk": 1
|
||||
|
|
398
judge/forms.py
398
judge/forms.py
|
@ -1,27 +1,14 @@
|
|||
import os
|
||||
import secrets
|
||||
from operator import attrgetter
|
||||
import pyotp
|
||||
import time
|
||||
import datetime
|
||||
|
||||
import pyotp
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db.models import Q
|
||||
from django.forms import (
|
||||
CharField,
|
||||
ChoiceField,
|
||||
Form,
|
||||
ModelForm,
|
||||
formset_factory,
|
||||
BaseModelFormSet,
|
||||
FileField,
|
||||
)
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.forms import CharField, ChoiceField, Form, ModelForm
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -29,7 +16,6 @@ from django_ace import AceWidget
|
|||
from judge.models import (
|
||||
Contest,
|
||||
Language,
|
||||
TestFormatterModel,
|
||||
Organization,
|
||||
PrivateMessage,
|
||||
Problem,
|
||||
|
@ -37,22 +23,14 @@ from judge.models import (
|
|||
Profile,
|
||||
Submission,
|
||||
BlogPost,
|
||||
ContestProblem,
|
||||
TestFormatterModel,
|
||||
ProfileInfo,
|
||||
)
|
||||
|
||||
from judge.utils.subscription import newsletter_id
|
||||
from judge.widgets import (
|
||||
HeavyPreviewPageDownWidget,
|
||||
MathJaxPagedownWidget,
|
||||
PagedownWidget,
|
||||
Select2MultipleWidget,
|
||||
Select2Widget,
|
||||
HeavySelect2MultipleWidget,
|
||||
HeavySelect2Widget,
|
||||
Select2MultipleWidget,
|
||||
DateTimePickerWidget,
|
||||
ImageWidget,
|
||||
DatePickerWidget,
|
||||
)
|
||||
|
||||
|
||||
|
@ -62,70 +40,63 @@ def fix_unicode(string, unsafe=tuple("\u202a\u202b\u202d\u202e")):
|
|||
)
|
||||
|
||||
|
||||
class UserForm(ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"first_name",
|
||||
"last_name",
|
||||
]
|
||||
|
||||
|
||||
class ProfileInfoForm(ModelForm):
|
||||
class Meta:
|
||||
model = ProfileInfo
|
||||
fields = ["tshirt_size", "date_of_birth", "address"]
|
||||
widgets = {
|
||||
"tshirt_size": Select2Widget(attrs={"style": "width:100%"}),
|
||||
"date_of_birth": DatePickerWidget,
|
||||
"address": forms.TextInput(attrs={"style": "width:100%"}),
|
||||
}
|
||||
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
if newsletter_id is not None:
|
||||
newsletter = forms.BooleanField(
|
||||
label=_("Subscribe to contest updates"), initial=False, required=False
|
||||
)
|
||||
test_site = forms.BooleanField(
|
||||
label=_("Enable experimental features"), initial=False, required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = [
|
||||
"about",
|
||||
"organizations",
|
||||
"timezone",
|
||||
"language",
|
||||
"ace_theme",
|
||||
"profile_image",
|
||||
"css_background",
|
||||
"user_script",
|
||||
]
|
||||
widgets = {
|
||||
"user_script": AceWidget(theme="github"),
|
||||
"timezone": Select2Widget(attrs={"style": "width:200px"}),
|
||||
"language": Select2Widget(attrs={"style": "width:200px"}),
|
||||
"ace_theme": Select2Widget(attrs={"style": "width:200px"}),
|
||||
"profile_image": ImageWidget,
|
||||
"css_background": forms.TextInput(),
|
||||
}
|
||||
|
||||
has_math_config = bool(settings.MATHOID_URL)
|
||||
if has_math_config:
|
||||
fields.append("math_engine")
|
||||
widgets["math_engine"] = Select2Widget(attrs={"style": "width:200px"})
|
||||
|
||||
if HeavyPreviewPageDownWidget is not None:
|
||||
widgets["about"] = HeavyPreviewPageDownWidget(
|
||||
preview=reverse_lazy("profile_preview"),
|
||||
attrs={"style": "max-width:700px;min-width:700px;width:700px"},
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
organizations = self.cleaned_data.get("organizations") or []
|
||||
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
|
||||
|
||||
if sum(org.is_open for org in organizations) > max_orgs:
|
||||
raise ValidationError(
|
||||
_("You may not be part of more than {count} public groups.").format(
|
||||
count=max_orgs
|
||||
)
|
||||
)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop("user", None)
|
||||
super(ProfileForm, self).__init__(*args, **kwargs)
|
||||
self.fields["profile_image"].required = False
|
||||
|
||||
def clean_profile_image(self):
|
||||
profile_image = self.cleaned_data.get("profile_image")
|
||||
if profile_image:
|
||||
if profile_image.size > 5 * 1024 * 1024:
|
||||
raise ValidationError(
|
||||
_("File size exceeds the maximum allowed limit of 5MB.")
|
||||
)
|
||||
return profile_image
|
||||
|
||||
|
||||
def file_size_validator(file):
|
||||
limit = 10 * 1024 * 1024
|
||||
if file.size > limit:
|
||||
raise ValidationError("File too large. Size should not exceed 10MB.")
|
||||
if not user.has_perm("judge.edit_all_organization"):
|
||||
self.fields["organizations"].queryset = Organization.objects.filter(
|
||||
Q(is_open=True) | Q(id__in=user.profile.organizations.all()),
|
||||
)
|
||||
|
||||
|
||||
class ProblemSubmitForm(ModelForm):
|
||||
|
@ -133,13 +104,9 @@ class ProblemSubmitForm(ModelForm):
|
|||
max_length=65536, widget=AceWidget(theme="twilight", no_ace_media=True)
|
||||
)
|
||||
judge = ChoiceField(choices=(), widget=forms.HiddenInput(), required=False)
|
||||
source_file = FileField(required=False, validators=[file_size_validator])
|
||||
|
||||
def __init__(self, *args, judge_choices=(), request=None, problem=None, **kwargs):
|
||||
def __init__(self, *args, judge_choices=(), **kwargs):
|
||||
super(ProblemSubmitForm, self).__init__(*args, **kwargs)
|
||||
self.source_file_name = None
|
||||
self.request = request
|
||||
self.problem = problem
|
||||
self.fields["language"].empty_label = None
|
||||
self.fields["language"].label_from_instance = attrgetter("display_name")
|
||||
self.fields["language"].queryset = Language.objects.filter(
|
||||
|
@ -152,36 +119,6 @@ class ProblemSubmitForm(ModelForm):
|
|||
)
|
||||
self.fields["judge"].choices = judge_choices
|
||||
|
||||
def allow_url_as_source(self):
|
||||
key = self.cleaned_data["language"].key
|
||||
filename = self.files["source_file"].name
|
||||
if key == "OUTPUT" and self.problem.data_files.output_only:
|
||||
return filename.endswith(".zip")
|
||||
if key == "SCAT":
|
||||
return filename.endswith(".sb3")
|
||||
return False
|
||||
|
||||
def clean(self):
|
||||
if "source_file" in self.files:
|
||||
if self.allow_url_as_source():
|
||||
filename = self.files["source_file"].name
|
||||
now = datetime.datetime.now()
|
||||
timestamp = str(int(time.mktime(now.timetuple())))
|
||||
self.source_file_name = (
|
||||
timestamp + secrets.token_hex(5) + "." + filename.split(".")[-1]
|
||||
)
|
||||
filepath = os.path.join(
|
||||
settings.DMOJ_SUBMISSION_ROOT, self.source_file_name
|
||||
)
|
||||
with open(filepath, "wb+") as destination:
|
||||
for chunk in self.files["source_file"].chunks():
|
||||
destination.write(chunk)
|
||||
self.cleaned_data["source"] = self.request.build_absolute_uri(
|
||||
reverse("submission_source_file", args=(self.source_file_name,))
|
||||
)
|
||||
del self.files["source_file"]
|
||||
return self.cleaned_data
|
||||
|
||||
class Meta:
|
||||
model = Submission
|
||||
fields = ["language"]
|
||||
|
@ -190,176 +127,13 @@ class ProblemSubmitForm(ModelForm):
|
|||
class EditOrganizationForm(ModelForm):
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = [
|
||||
"name",
|
||||
"slug",
|
||||
"short_name",
|
||||
"about",
|
||||
"organization_image",
|
||||
"admins",
|
||||
"is_open",
|
||||
]
|
||||
widgets = {
|
||||
"admins": Select2MultipleWidget(),
|
||||
"organization_image": ImageWidget,
|
||||
}
|
||||
fields = ["about", "logo_override_image", "admins", "is_open"]
|
||||
widgets = {"admins": Select2MultipleWidget()}
|
||||
if HeavyPreviewPageDownWidget is not None:
|
||||
widgets["about"] = HeavyPreviewPageDownWidget(
|
||||
preview=reverse_lazy("organization_preview")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditOrganizationForm, self).__init__(*args, **kwargs)
|
||||
self.fields["organization_image"].required = False
|
||||
|
||||
def clean_organization_image(self):
|
||||
organization_image = self.cleaned_data.get("organization_image")
|
||||
if organization_image:
|
||||
if organization_image.size > 5 * 1024 * 1024:
|
||||
raise ValidationError(
|
||||
_("File size exceeds the maximum allowed limit of 5MB.")
|
||||
)
|
||||
return organization_image
|
||||
|
||||
|
||||
class AddOrganizationForm(ModelForm):
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = [
|
||||
"name",
|
||||
"slug",
|
||||
"short_name",
|
||||
"about",
|
||||
"organization_image",
|
||||
"is_open",
|
||||
]
|
||||
widgets = {}
|
||||
if HeavyPreviewPageDownWidget is not None:
|
||||
widgets["about"] = HeavyPreviewPageDownWidget(
|
||||
preview=reverse_lazy("organization_preview")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop("request", None)
|
||||
super(AddOrganizationForm, self).__init__(*args, **kwargs)
|
||||
self.fields["organization_image"].required = False
|
||||
|
||||
def save(self, commit=True):
|
||||
res = super(AddOrganizationForm, self).save(commit=False)
|
||||
res.registrant = self.request.profile
|
||||
if commit:
|
||||
res.save()
|
||||
return res
|
||||
|
||||
|
||||
class AddOrganizationContestForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop("request", None)
|
||||
super(AddOrganizationContestForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
contest = super(AddOrganizationContestForm, self).save(commit=False)
|
||||
old_save_m2m = self.save_m2m
|
||||
|
||||
def save_m2m():
|
||||
for i, problem in enumerate(self.cleaned_data["problems"]):
|
||||
contest_problem = ContestProblem(
|
||||
contest=contest, problem=problem, points=100, order=i + 1
|
||||
)
|
||||
contest_problem.save()
|
||||
contest.contest_problems.add(contest_problem)
|
||||
old_save_m2m()
|
||||
|
||||
self.save_m2m = save_m2m
|
||||
contest.save()
|
||||
self.save_m2m()
|
||||
return contest
|
||||
|
||||
class Meta:
|
||||
model = Contest
|
||||
fields = (
|
||||
"key",
|
||||
"name",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"problems",
|
||||
)
|
||||
widgets = {
|
||||
"start_time": DateTimePickerWidget(),
|
||||
"end_time": DateTimePickerWidget(),
|
||||
"problems": HeavySelect2MultipleWidget(data_view="problem_select2"),
|
||||
}
|
||||
|
||||
|
||||
class EditOrganizationContestForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.org_id = kwargs.pop("org_id", 0)
|
||||
super(EditOrganizationContestForm, self).__init__(*args, **kwargs)
|
||||
for field in [
|
||||
"authors",
|
||||
"curators",
|
||||
"testers",
|
||||
"private_contestants",
|
||||
"banned_users",
|
||||
"view_contest_scoreboard",
|
||||
]:
|
||||
self.fields[field].widget.data_url = (
|
||||
self.fields[field].widget.get_url() + f"?org_id={self.org_id}"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Contest
|
||||
fields = (
|
||||
"is_visible",
|
||||
"key",
|
||||
"name",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"format_name",
|
||||
"authors",
|
||||
"curators",
|
||||
"testers",
|
||||
"time_limit",
|
||||
"freeze_after",
|
||||
"use_clarifications",
|
||||
"hide_problem_tags",
|
||||
"public_scoreboard",
|
||||
"scoreboard_visibility",
|
||||
"points_precision",
|
||||
"rate_limit",
|
||||
"description",
|
||||
"og_image",
|
||||
"logo_override_image",
|
||||
"summary",
|
||||
"access_code",
|
||||
"private_contestants",
|
||||
"view_contest_scoreboard",
|
||||
"banned_users",
|
||||
)
|
||||
widgets = {
|
||||
"authors": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||
"curators": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||
"testers": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||
"private_contestants": HeavySelect2MultipleWidget(
|
||||
data_view="profile_select2"
|
||||
),
|
||||
"banned_users": HeavySelect2MultipleWidget(data_view="profile_select2"),
|
||||
"view_contest_scoreboard": HeavySelect2MultipleWidget(
|
||||
data_view="profile_select2"
|
||||
),
|
||||
"organizations": HeavySelect2MultipleWidget(
|
||||
data_view="organization_select2"
|
||||
),
|
||||
"tags": Select2MultipleWidget,
|
||||
"description": HeavyPreviewPageDownWidget(
|
||||
preview=reverse_lazy("contest_preview")
|
||||
),
|
||||
"start_time": DateTimePickerWidget(),
|
||||
"end_time": DateTimePickerWidget(),
|
||||
"format_name": Select2Widget(),
|
||||
"scoreboard_visibility": Select2Widget(),
|
||||
}
|
||||
|
||||
|
||||
class AddOrganizationMemberForm(ModelForm):
|
||||
new_users = CharField(
|
||||
|
@ -369,7 +143,7 @@ class AddOrganizationMemberForm(ModelForm):
|
|||
label=_("New users"),
|
||||
)
|
||||
|
||||
def clean_new_users(self):
|
||||
def clean(self):
|
||||
new_users = self.cleaned_data.get("new_users") or ""
|
||||
usernames = new_users.split()
|
||||
invalid_usernames = []
|
||||
|
@ -387,7 +161,8 @@ class AddOrganizationMemberForm(ModelForm):
|
|||
usernames=str(invalid_usernames)
|
||||
)
|
||||
)
|
||||
return valid_usernames
|
||||
self.cleaned_data["new_users"] = valid_usernames
|
||||
return self.cleaned_data
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
|
@ -435,15 +210,13 @@ class NewMessageForm(ModelForm):
|
|||
fields = ["title", "content"]
|
||||
widgets = {}
|
||||
if PagedownWidget is not None:
|
||||
widgets["content"] = PagedownWidget()
|
||||
widgets["content"] = MathJaxPagedownWidget()
|
||||
|
||||
|
||||
class CustomAuthenticationForm(AuthenticationForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CustomAuthenticationForm, self).__init__(*args, **kwargs)
|
||||
self.fields["username"].widget.attrs.update(
|
||||
{"placeholder": _("Username/Email")}
|
||||
)
|
||||
self.fields["username"].widget.attrs.update({"placeholder": _("Username")})
|
||||
self.fields["password"].widget.attrs.update({"placeholder": _("Password")})
|
||||
|
||||
self.has_google_auth = self._has_social_auth("GOOGLE_OAUTH2")
|
||||
|
@ -506,15 +279,6 @@ class ContestCloneForm(Form):
|
|||
max_length=20,
|
||||
validators=[RegexValidator("^[a-z0-9]+$", _("Contest id must be ^[a-z0-9]+$"))],
|
||||
)
|
||||
organization = ChoiceField(choices=(), required=True)
|
||||
|
||||
def __init__(self, *args, org_choices=(), profile=None, **kwargs):
|
||||
super(ContestCloneForm, self).__init__(*args, **kwargs)
|
||||
self.fields["organization"].widget = Select2Widget(
|
||||
attrs={"style": "width: 100%", "data-placeholder": _("Group")},
|
||||
)
|
||||
self.fields["organization"].choices = org_choices
|
||||
self.profile = profile
|
||||
|
||||
def clean_key(self):
|
||||
key = self.cleaned_data["key"]
|
||||
|
@ -522,78 +286,8 @@ class ContestCloneForm(Form):
|
|||
raise ValidationError(_("Contest with key already exists."))
|
||||
return key
|
||||
|
||||
def clean_organization(self):
|
||||
organization_id = self.cleaned_data["organization"]
|
||||
try:
|
||||
organization = Organization.objects.get(id=organization_id)
|
||||
except Exception:
|
||||
raise ValidationError(_("Group doesn't exist."))
|
||||
if not organization.admins.filter(id=self.profile.id).exists():
|
||||
raise ValidationError(_("You don't have permission in this group."))
|
||||
return organization
|
||||
|
||||
|
||||
class ProblemPointsVoteForm(ModelForm):
|
||||
class Meta:
|
||||
model = ProblemPointsVote
|
||||
fields = ["points"]
|
||||
|
||||
|
||||
class ContestProblemForm(ModelForm):
|
||||
class Meta:
|
||||
model = ContestProblem
|
||||
fields = (
|
||||
"order",
|
||||
"problem",
|
||||
"points",
|
||||
"partial",
|
||||
"show_testcases",
|
||||
"max_submissions",
|
||||
)
|
||||
widgets = {
|
||||
"problem": HeavySelect2Widget(
|
||||
data_view="problem_select2", attrs={"style": "width: 100%"}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class ContestProblemModelFormSet(BaseModelFormSet):
|
||||
def is_valid(self):
|
||||
valid = super().is_valid()
|
||||
|
||||
if not valid:
|
||||
return valid
|
||||
|
||||
problems = set()
|
||||
duplicates = []
|
||||
|
||||
for form in self.forms:
|
||||
if form.cleaned_data and not form.cleaned_data.get("DELETE", False):
|
||||
problem = form.cleaned_data.get("problem")
|
||||
if problem in problems:
|
||||
duplicates.append(problem)
|
||||
else:
|
||||
problems.add(problem)
|
||||
|
||||
if duplicates:
|
||||
for form in self.forms:
|
||||
problem = form.cleaned_data.get("problem")
|
||||
if problem in duplicates:
|
||||
form.add_error("problem", _("This problem is duplicated."))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ContestProblemFormSet(
|
||||
formset_factory(
|
||||
ContestProblemForm, formset=ContestProblemModelFormSet, extra=6, can_delete=True
|
||||
)
|
||||
):
|
||||
model = ContestProblem
|
||||
|
||||
|
||||
class TestFormatterForm(ModelForm):
|
||||
class Meta:
|
||||
model = TestFormatterModel
|
||||
fields = ["file"]
|
||||
|
|
|
@ -1,13 +1,47 @@
|
|||
from django.utils.html import escape, mark_safe
|
||||
from judge.markdown import markdown
|
||||
|
||||
__all__ = ["highlight_code"]
|
||||
|
||||
|
||||
def highlight_code(code, language, linenos=True, title=None):
|
||||
linenos_option = 'linenums="1"' if linenos else ""
|
||||
title_option = f'title="{title}"' if title else ""
|
||||
options = f"{{.{language} {linenos_option} {title_option}}}"
|
||||
def _make_pre_code(code):
|
||||
return mark_safe("<pre>" + escape(code) + "</pre>")
|
||||
|
||||
value = f"```{options}\n{code}\n```\n"
|
||||
return mark_safe(markdown(value))
|
||||
|
||||
def _wrap_code(inner):
|
||||
yield 0, "<code>"
|
||||
for tup in inner:
|
||||
yield tup
|
||||
yield 0, "</code>"
|
||||
|
||||
|
||||
try:
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters.html
|
||||
import pygments.util
|
||||
except ImportError:
|
||||
|
||||
def highlight_code(code, language, cssclass=None):
|
||||
return _make_pre_code(code)
|
||||
|
||||
else:
|
||||
|
||||
class HtmlCodeFormatter(pygments.formatters.HtmlFormatter):
|
||||
def wrap(self, source, outfile):
|
||||
return self._wrap_div(self._wrap_pre(_wrap_code(source)))
|
||||
|
||||
def highlight_code(code, language, cssclass="codehilite", linenos=True):
|
||||
try:
|
||||
lexer = pygments.lexers.get_lexer_by_name(language)
|
||||
except pygments.util.ClassNotFound:
|
||||
return _make_pre_code(code)
|
||||
|
||||
if linenos:
|
||||
return mark_safe(
|
||||
pygments.highlight(
|
||||
code, lexer, HtmlCodeFormatter(cssclass=cssclass, linenos="table")
|
||||
)
|
||||
)
|
||||
return mark_safe(
|
||||
pygments.highlight(code, lexer, HtmlCodeFormatter(cssclass=cssclass))
|
||||
)
|
||||
|
|
|
@ -21,8 +21,8 @@ from . import (
|
|||
render,
|
||||
social,
|
||||
spaceless,
|
||||
submission,
|
||||
timedelta,
|
||||
comment,
|
||||
)
|
||||
from . import registry
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
from . import registry
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from judge.models.comment import get_visible_comment_count
|
||||
from judge.caching import cache_wrapper
|
||||
|
||||
|
||||
@registry.function
|
||||
def comment_count(obj):
|
||||
content_type = ContentType.objects.get_for_model(obj)
|
||||
return get_visible_comment_count(content_type, obj.pk)
|
|
@ -23,5 +23,5 @@ registry.filter(localtime_wrapper(time))
|
|||
|
||||
@registry.function
|
||||
@registry.render_with("widgets/relative-time.html")
|
||||
def relative_time(time, format=_("N j, Y, g:i a"), rel=_("{time}"), abs=_("{time}")):
|
||||
def relative_time(time, format=_("N j, Y, g:i a"), rel=_("{time}"), abs=_("on {time}")):
|
||||
return {"time": time, "format": format, "rel_format": rel, "abs_format": abs}
|
||||
|
|
|
@ -9,16 +9,14 @@ from . import registry
|
|||
|
||||
|
||||
@registry.function
|
||||
def gravatar(profile, size=80, default=None, profile_image=None, email=None):
|
||||
if profile and not profile.is_muted:
|
||||
if profile_image:
|
||||
return profile_image
|
||||
if profile and profile.profile_image_url:
|
||||
return profile.profile_image_url
|
||||
if profile:
|
||||
email = email or profile.email
|
||||
def gravatar(email, size=80, default=None):
|
||||
if isinstance(email, Profile):
|
||||
if default is None:
|
||||
default = profile.is_muted
|
||||
default = email.mute
|
||||
email = email.user.email
|
||||
elif isinstance(email, AbstractUser):
|
||||
email = email.email
|
||||
|
||||
gravatar_url = (
|
||||
"//www.gravatar.com/avatar/"
|
||||
+ hashlib.md5(utf8bytes(email.strip().lower())).hexdigest()
|
||||
|
|
|
@ -1,7 +1,172 @@
|
|||
import logging
|
||||
import re
|
||||
from html.parser import HTMLParser
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import mistune
|
||||
from django.conf import settings
|
||||
from jinja2 import Markup
|
||||
from lxml import html
|
||||
from lxml.etree import ParserError, XMLSyntaxError
|
||||
|
||||
from judge.highlight_code import highlight_code
|
||||
from judge.jinja2.markdown.lazy_load import lazy_load as lazy_load_processor
|
||||
from judge.jinja2.markdown.math import MathInlineGrammar, MathInlineLexer, MathRenderer
|
||||
from judge.jinja2.markdown.spoiler import (
|
||||
SpoilerInlineGrammar,
|
||||
SpoilerInlineLexer,
|
||||
SpoilerRenderer,
|
||||
)
|
||||
from judge.utils.camo import client as camo_client
|
||||
from judge.utils.texoid import TEXOID_ENABLED, TexoidRenderer
|
||||
from .. import registry
|
||||
from judge.markdown import markdown as _markdown
|
||||
|
||||
logger = logging.getLogger("judge.html")
|
||||
|
||||
NOFOLLOW_WHITELIST = settings.NOFOLLOW_EXCLUDED
|
||||
|
||||
|
||||
class CodeSafeInlineGrammar(mistune.InlineGrammar):
|
||||
double_emphasis = re.compile(r"^\*{2}([\s\S]+?)()\*{2}(?!\*)") # **word**
|
||||
emphasis = re.compile(r"^\*((?:\*\*|[^\*])+?)()\*(?!\*)") # *word*
|
||||
|
||||
|
||||
class AwesomeInlineGrammar(
|
||||
MathInlineGrammar, SpoilerInlineGrammar, CodeSafeInlineGrammar
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class AwesomeInlineLexer(MathInlineLexer, SpoilerInlineLexer, mistune.InlineLexer):
|
||||
grammar_class = AwesomeInlineGrammar
|
||||
|
||||
|
||||
class AwesomeRenderer(MathRenderer, SpoilerRenderer, mistune.Renderer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.nofollow = kwargs.pop("nofollow", True)
|
||||
self.texoid = TexoidRenderer() if kwargs.pop("texoid", False) else None
|
||||
self.parser = HTMLParser()
|
||||
super(AwesomeRenderer, self).__init__(*args, **kwargs)
|
||||
|
||||
def _link_rel(self, href):
|
||||
if href:
|
||||
try:
|
||||
url = urlparse(href)
|
||||
except ValueError:
|
||||
return ' rel="nofollow"'
|
||||
else:
|
||||
if url.netloc and url.netloc not in NOFOLLOW_WHITELIST:
|
||||
return ' rel="nofollow"'
|
||||
return ""
|
||||
|
||||
def autolink(self, link, is_email=False):
|
||||
text = link = mistune.escape(link)
|
||||
if is_email:
|
||||
link = "mailto:%s" % link
|
||||
return '<a href="%s"%s>%s</a>' % (link, self._link_rel(link), text)
|
||||
|
||||
def table(self, header, body):
|
||||
return (
|
||||
'<table class="table">\n<thead>%s</thead>\n'
|
||||
"<tbody>\n%s</tbody>\n</table>\n"
|
||||
) % (header, body)
|
||||
|
||||
def link(self, link, title, text):
|
||||
link = mistune.escape_link(link)
|
||||
if not title:
|
||||
return '<a href="%s"%s>%s</a>' % (link, self._link_rel(link), text)
|
||||
title = mistune.escape(title, quote=True)
|
||||
return '<a href="%s" title="%s"%s>%s</a>' % (
|
||||
link,
|
||||
title,
|
||||
self._link_rel(link),
|
||||
text,
|
||||
)
|
||||
|
||||
def block_code(self, code, lang=None):
|
||||
if not lang:
|
||||
return "\n<pre><code>%s</code></pre>\n" % mistune.escape(code).rstrip()
|
||||
return highlight_code(code, lang)
|
||||
|
||||
def block_html(self, html):
|
||||
if self.texoid and html.startswith("<latex"):
|
||||
attr = html[6 : html.index(">")]
|
||||
latex = html[html.index(">") + 1 : html.rindex("<")]
|
||||
latex = self.parser.unescape(latex)
|
||||
result = self.texoid.get_result(latex)
|
||||
if not result:
|
||||
return "<pre>%s</pre>" % mistune.escape(latex, smart_amp=False)
|
||||
elif "error" not in result:
|
||||
img = (
|
||||
'''<img src="%(svg)s" onerror="this.src='%(png)s';this.onerror=null"'''
|
||||
'width="%(width)s" height="%(height)s"%(tail)s>'
|
||||
) % {
|
||||
"svg": result["svg"],
|
||||
"png": result["png"],
|
||||
"width": result["meta"]["width"],
|
||||
"height": result["meta"]["height"],
|
||||
"tail": " /" if self.options.get("use_xhtml") else "",
|
||||
}
|
||||
style = [
|
||||
"max-width: 100%",
|
||||
"height: %s" % result["meta"]["height"],
|
||||
"max-height: %s" % result["meta"]["height"],
|
||||
"width: %s" % result["meta"]["height"],
|
||||
]
|
||||
if "inline" in attr:
|
||||
tag = "span"
|
||||
else:
|
||||
tag = "div"
|
||||
style += ["text-align: center"]
|
||||
return '<%s style="%s">%s</%s>' % (tag, ";".join(style), img, tag)
|
||||
else:
|
||||
return "<pre>%s</pre>" % mistune.escape(
|
||||
result["error"], smart_amp=False
|
||||
)
|
||||
return super(AwesomeRenderer, self).block_html(html)
|
||||
|
||||
def header(self, text, level, *args, **kwargs):
|
||||
return super(AwesomeRenderer, self).header(text, level + 2, *args, **kwargs)
|
||||
|
||||
|
||||
@registry.filter
|
||||
def markdown(value, lazy_load=False):
|
||||
return _markdown(value, lazy_load)
|
||||
def markdown(value, style, math_engine=None, lazy_load=False):
|
||||
styles = settings.MARKDOWN_STYLES.get(style, settings.MARKDOWN_DEFAULT_STYLE)
|
||||
escape = styles.get("safe_mode", True)
|
||||
nofollow = styles.get("nofollow", True)
|
||||
texoid = TEXOID_ENABLED and styles.get("texoid", False)
|
||||
math = hasattr(settings, "MATHOID_URL") and styles.get("math", False)
|
||||
|
||||
post_processors = []
|
||||
if styles.get("use_camo", False) and camo_client is not None:
|
||||
post_processors.append(camo_client.update_tree)
|
||||
if lazy_load:
|
||||
post_processors.append(lazy_load_processor)
|
||||
|
||||
renderer = AwesomeRenderer(
|
||||
escape=escape,
|
||||
nofollow=nofollow,
|
||||
texoid=texoid,
|
||||
math=math and math_engine is not None,
|
||||
math_engine=math_engine,
|
||||
)
|
||||
markdown = mistune.Markdown(
|
||||
renderer=renderer,
|
||||
inline=AwesomeInlineLexer,
|
||||
parse_block_html=1,
|
||||
parse_inline_html=1,
|
||||
)
|
||||
result = markdown(value)
|
||||
if post_processors:
|
||||
try:
|
||||
tree = html.fromstring(result, parser=html.HTMLParser(recover=True))
|
||||
except (XMLSyntaxError, ParserError) as e:
|
||||
if result and (
|
||||
not isinstance(e, ParserError) or e.args[0] != "Document is empty"
|
||||
):
|
||||
logger.exception("Failed to parse HTML string")
|
||||
tree = html.Element("div")
|
||||
for processor in post_processors:
|
||||
processor(tree)
|
||||
result = html.tostring(tree, encoding="unicode")
|
||||
return Markup(result)
|
||||
|
|
20
judge/jinja2/markdown/lazy_load.py
Normal file
20
judge/jinja2/markdown/lazy_load.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from django.templatetags.static import static
|
||||
from lxml import html
|
||||
|
||||
|
||||
def lazy_load(tree):
|
||||
blank = static("blank.gif")
|
||||
for img in tree.xpath(".//img"):
|
||||
src = img.get("src", "")
|
||||
if src.startswith("data") or "-math" in img.get("class", ""):
|
||||
continue
|
||||
noscript = html.Element("noscript")
|
||||
copy = deepcopy(img)
|
||||
copy.tail = ""
|
||||
noscript.append(copy)
|
||||
img.addprevious(noscript)
|
||||
img.set("data-src", src)
|
||||
img.set("src", blank)
|
||||
img.set("class", img.get("class") + " unveil" if img.get("class") else "unveil")
|
69
judge/jinja2/markdown/math.py
Normal file
69
judge/jinja2/markdown/math.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import re
|
||||
|
||||
import mistune
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from judge.utils.mathoid import MathoidMathParser
|
||||
|
||||
mistune._pre_tags.append("latex")
|
||||
|
||||
|
||||
class MathInlineGrammar(mistune.InlineGrammar):
|
||||
block_math = re.compile(r"^\$\$(.*?)\$\$|^\\\[(.*?)\\\]", re.DOTALL)
|
||||
math = re.compile(r"^~(.*?)~|^\\\((.*?)\\\)", re.DOTALL)
|
||||
text = re.compile(r"^[\s\S]+?(?=[\\<!\[_*`~$]|\\[\[(]|https?://| {2,}\n|$)")
|
||||
|
||||
|
||||
class MathInlineLexer(mistune.InlineLexer):
|
||||
grammar_class = MathInlineGrammar
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.default_rules = self.default_rules[:]
|
||||
self.inline_html_rules = self.default_rules
|
||||
self.default_rules.insert(self.default_rules.index("strikethrough") + 1, "math")
|
||||
self.default_rules.insert(
|
||||
self.default_rules.index("strikethrough") + 1, "block_math"
|
||||
)
|
||||
super(MathInlineLexer, self).__init__(*args, **kwargs)
|
||||
|
||||
def output_block_math(self, m):
|
||||
return self.renderer.block_math(m.group(1) or m.group(2))
|
||||
|
||||
def output_math(self, m):
|
||||
return self.renderer.math(m.group(1) or m.group(2))
|
||||
|
||||
def output_inline_html(self, m):
|
||||
tag = m.group(1)
|
||||
text = m.group(3)
|
||||
if self._parse_inline_html and text:
|
||||
if tag == "a":
|
||||
self._in_link = True
|
||||
text = self.output(text)
|
||||
self._in_link = False
|
||||
else:
|
||||
text = self.output(text)
|
||||
extra = m.group(2) or ""
|
||||
html = "<%s%s>%s</%s>" % (tag, extra, text, tag)
|
||||
else:
|
||||
html = m.group(0)
|
||||
return self.renderer.inline_html(html)
|
||||
|
||||
|
||||
class MathRenderer(mistune.Renderer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.pop("math", False) and settings.MATHOID_URL != False:
|
||||
self.mathoid = MathoidMathParser(kwargs.pop("math_engine", None) or "svg")
|
||||
else:
|
||||
self.mathoid = None
|
||||
super(MathRenderer, self).__init__(*args, **kwargs)
|
||||
|
||||
def block_math(self, math):
|
||||
if self.mathoid is None or not math:
|
||||
return r"\[%s\]" % mistune.escape(str(math))
|
||||
return self.mathoid.display_math(math)
|
||||
|
||||
def math(self, math):
|
||||
if self.mathoid is None or not math:
|
||||
return r"\(%s\)" % mistune.escape(str(math))
|
||||
return self.mathoid.inline_math(math)
|
30
judge/jinja2/markdown/spoiler.py
Normal file
30
judge/jinja2/markdown/spoiler.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import re
|
||||
import mistune
|
||||
|
||||
|
||||
class SpoilerInlineGrammar(mistune.InlineGrammar):
|
||||
spoiler = re.compile(r"^\|\|(.+?)\s+([\s\S]+?)\s*\|\|")
|
||||
|
||||
|
||||
class SpoilerInlineLexer(mistune.InlineLexer):
|
||||
grammar_class = SpoilerInlineGrammar
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.default_rules.insert(0, "spoiler")
|
||||
super(SpoilerInlineLexer, self).__init__(*args, **kwargs)
|
||||
|
||||
def output_spoiler(self, m):
|
||||
return self.renderer.spoiler(m.group(1), m.group(2))
|
||||
|
||||
|
||||
class SpoilerRenderer(mistune.Renderer):
|
||||
def spoiler(self, summary, text):
|
||||
return """<details>
|
||||
<summary style="color: brown">
|
||||
<span class="spoiler-summary">%s</span>
|
||||
</summary>
|
||||
<div class="spoiler-text">%s</div>
|
||||
</details>""" % (
|
||||
summary,
|
||||
text,
|
||||
)
|
|
@ -1,3 +1,5 @@
|
|||
from django.utils import six
|
||||
|
||||
from judge.ratings import rating_class, rating_name, rating_progress
|
||||
from . import registry
|
||||
|
||||
|
@ -6,7 +8,7 @@ def _get_rating_value(func, obj):
|
|||
if obj is None:
|
||||
return None
|
||||
|
||||
if isinstance(obj, int):
|
||||
if isinstance(obj, six.integer_types):
|
||||
return func(obj)
|
||||
else:
|
||||
return func(obj.rating)
|
||||
|
|
|
@ -155,16 +155,16 @@ def item_title(item):
|
|||
|
||||
@registry.function
|
||||
@registry.render_with("user/link.html")
|
||||
def link_user(user, show_image=False):
|
||||
def link_user(user):
|
||||
if isinstance(user, Profile):
|
||||
profile = user
|
||||
user, profile = user.user, user
|
||||
elif isinstance(user, AbstractUser):
|
||||
profile = user.profile
|
||||
elif isinstance(user, int):
|
||||
profile = Profile(id=user)
|
||||
elif type(user).__name__ == "ContestRankingProfile":
|
||||
user, profile = user.user, user
|
||||
else:
|
||||
raise ValueError("Expected profile or user, got %s" % (type(user),))
|
||||
return {"profile": profile, "show_image": show_image}
|
||||
return {"user": user, "profile": profile}
|
||||
|
||||
|
||||
@registry.function
|
||||
|
|
|
@ -48,9 +48,5 @@ for name, template, url_func in SHARES:
|
|||
@registry.function
|
||||
def recaptcha_init(language=None):
|
||||
return get_template("snowpenguin/recaptcha/recaptcha_init.html").render(
|
||||
{
|
||||
"explicit": False,
|
||||
"language": language,
|
||||
"recaptcha_host": "https://google.com",
|
||||
}
|
||||
{"explicit": False, "language": language}
|
||||
)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import re
|
||||
|
||||
from jinja2 import nodes
|
||||
from jinja2 import Markup, nodes
|
||||
from jinja2.ext import Extension
|
||||
from markupsafe import Markup
|
||||
|
||||
|
||||
class SpacelessExtension(Extension):
|
||||
|
|
30
judge/jinja2/submission.py
Normal file
30
judge/jinja2/submission.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from . import registry
|
||||
|
||||
|
||||
@registry.function
|
||||
def submission_layout(
|
||||
submission, profile_id, user, editable_problem_ids, completed_problem_ids
|
||||
):
|
||||
problem_id = submission.problem_id
|
||||
can_view = False
|
||||
|
||||
if problem_id in editable_problem_ids:
|
||||
can_view = True
|
||||
|
||||
if profile_id == submission.user_id:
|
||||
can_view = True
|
||||
|
||||
if user.has_perm("judge.change_submission"):
|
||||
can_view = True
|
||||
|
||||
if submission.problem_id in completed_problem_ids:
|
||||
can_view |= (
|
||||
submission.problem.is_public or profile_id in submission.problem.tester_ids
|
||||
)
|
||||
|
||||
if not can_view and hasattr(submission, "contest"):
|
||||
contest = submission.contest.participation.contest
|
||||
if contest.is_editable_by(user):
|
||||
can_view = True
|
||||
|
||||
return can_view
|
|
@ -1,12 +0,0 @@
|
|||
import logging
|
||||
|
||||
error_log = logging.getLogger("judge.errors")
|
||||
debug_log = logging.getLogger("judge.debug")
|
||||
|
||||
|
||||
def log_exception(msg):
|
||||
error_log.exception(msg)
|
||||
|
||||
|
||||
def log_debug(category, data):
|
||||
debug_log.info(f"{category}: {data}")
|
|
@ -1,50 +1,44 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from judge.models import *
|
||||
from collections import defaultdict
|
||||
import csv
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
|
||||
|
||||
def gen_submissions():
|
||||
print("Generating submissions")
|
||||
query = """
|
||||
SELECT user_id as uid, problem_id as pid from
|
||||
(SELECT user_id, problem_id, max(date) as max_date
|
||||
from judge_submission
|
||||
group by user_id, problem_id) t
|
||||
order by user_id, -max_date;
|
||||
"""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
headers = [i[0] for i in cursor.description]
|
||||
with open(
|
||||
os.path.join(settings.ML_DATA_PATH, "submissions.csv"), "w"
|
||||
) as csvfile:
|
||||
f = csv.writer(csvfile)
|
||||
f.writerow(headers)
|
||||
for row in cursor.fetchall():
|
||||
f.writerow(row)
|
||||
headers = ["uid", "pid"]
|
||||
with open(os.path.join(settings.ML_DATA_PATH, "submissions.csv"), "w") as csvfile:
|
||||
f = csv.writer(csvfile)
|
||||
f.writerow(headers)
|
||||
|
||||
last_pid = defaultdict(int)
|
||||
for u in Profile.objects.all():
|
||||
used = set()
|
||||
print("Processing user", u.id)
|
||||
for s in Submission.objects.filter(user=u).order_by("-date"):
|
||||
if s.problem.id not in used:
|
||||
used.add(s.problem.id)
|
||||
f.writerow([u.id, s.problem.id])
|
||||
|
||||
|
||||
def gen_users():
|
||||
print("Generating users")
|
||||
headers = ["uid", "username", "rating", "points"]
|
||||
with open(os.path.join(settings.ML_DATA_PATH, "profiles.csv"), "w") as csvfile:
|
||||
f = csv.writer(csvfile)
|
||||
f.writerow(headers)
|
||||
|
||||
for u in Profile.objects.all().iterator():
|
||||
for u in Profile.objects.all():
|
||||
f.writerow([u.id, u.username, u.rating, u.performance_points])
|
||||
|
||||
|
||||
def gen_problems():
|
||||
print("Generating problems")
|
||||
headers = ["pid", "code", "name", "points", "url"]
|
||||
with open(os.path.join(settings.ML_DATA_PATH, "problems.csv"), "w") as csvfile:
|
||||
f = csv.writer(csvfile)
|
||||
f.writerow(headers)
|
||||
for p in Problem.objects.all().iterator():
|
||||
|
||||
for p in Problem.objects.all():
|
||||
f.writerow(
|
||||
[p.id, p.code, p.name, p.points, "lqdoj.edu.vn/problem/" + p.code]
|
||||
)
|
||||
|
|
|
@ -89,13 +89,14 @@ class Command(BaseCommand):
|
|||
if trans is None
|
||||
else trans.description,
|
||||
"url": "",
|
||||
"math_engine": maker.math_engine,
|
||||
}
|
||||
)
|
||||
.replace('"//', '"https://')
|
||||
.replace("'//", "'https://")
|
||||
)
|
||||
maker.title = problem_name
|
||||
for file in "style.css":
|
||||
for file in ("style.css", "pygment-github.css", "mathjax_config.js"):
|
||||
maker.load(file, os.path.join(settings.DMOJ_RESOURCES, file))
|
||||
maker.make(debug=True)
|
||||
if not maker.success:
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
import markdown as _markdown
|
||||
import bleach
|
||||
from django.utils.html import escape
|
||||
from bs4 import BeautifulSoup
|
||||
from pymdownx import superfences
|
||||
from django.conf import settings
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from judge.markdown_extensions import YouTubeExtension, EmoticonExtension
|
||||
|
||||
|
||||
EXTENSIONS = [
|
||||
"pymdownx.arithmatex",
|
||||
"pymdownx.magiclink",
|
||||
"pymdownx.betterem",
|
||||
"pymdownx.details",
|
||||
"pymdownx.emoji",
|
||||
"pymdownx.inlinehilite",
|
||||
"pymdownx.superfences",
|
||||
"pymdownx.highlight",
|
||||
"pymdownx.tasklist",
|
||||
"markdown.extensions.footnotes",
|
||||
"markdown.extensions.attr_list",
|
||||
"markdown.extensions.def_list",
|
||||
"markdown.extensions.tables",
|
||||
"markdown.extensions.admonition",
|
||||
"nl2br",
|
||||
"mdx_breakless_lists",
|
||||
YouTubeExtension(),
|
||||
EmoticonExtension(),
|
||||
]
|
||||
|
||||
EXTENSION_CONFIGS = {
|
||||
"pymdownx.arithmatex": {
|
||||
"generic": True,
|
||||
},
|
||||
"pymdownx.superfences": {
|
||||
"custom_fences": [
|
||||
{
|
||||
"name": "sample",
|
||||
"class": "no-border",
|
||||
"format": superfences.fence_code_format,
|
||||
}
|
||||
],
|
||||
},
|
||||
"pymdownx.highlight": {
|
||||
"auto_title": True,
|
||||
"auto_title_map": {
|
||||
"Text Only": "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ALLOWED_TAGS = list(bleach.sanitizer.ALLOWED_TAGS) + [
|
||||
"img",
|
||||
"center",
|
||||
"iframe",
|
||||
"div",
|
||||
"span",
|
||||
"table",
|
||||
"tr",
|
||||
"td",
|
||||
"th",
|
||||
"tr",
|
||||
"pre",
|
||||
"code",
|
||||
"p",
|
||||
"hr",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"thead",
|
||||
"tbody",
|
||||
"sup",
|
||||
"dl",
|
||||
"dt",
|
||||
"dd",
|
||||
"br",
|
||||
"details",
|
||||
"summary",
|
||||
]
|
||||
|
||||
ALLOWED_ATTRS = [
|
||||
"src",
|
||||
"width",
|
||||
"height",
|
||||
"href",
|
||||
"class",
|
||||
"open",
|
||||
"title",
|
||||
"frameborder",
|
||||
"allow",
|
||||
"allowfullscreen",
|
||||
"loading",
|
||||
]
|
||||
|
||||
|
||||
def _wrap_img_iframe_with_lazy_load(soup):
|
||||
for img in soup.findAll("img"):
|
||||
if img.get("src"):
|
||||
img["loading"] = "lazy"
|
||||
for img in soup.findAll("iframe"):
|
||||
if img.get("src"):
|
||||
img["loading"] = "lazy"
|
||||
return soup
|
||||
|
||||
|
||||
def _wrap_images_with_featherlight(soup):
|
||||
for img in soup.findAll("img"):
|
||||
if img.get("src"):
|
||||
link = soup.new_tag("a", href=img["src"], **{"data-featherlight": "image"})
|
||||
img.wrap(link)
|
||||
return soup
|
||||
|
||||
|
||||
def _open_external_links_in_new_tab(soup):
|
||||
domain = settings.SITE_DOMAIN.lower()
|
||||
for a in soup.findAll("a", href=True):
|
||||
href = a["href"]
|
||||
if href.startswith("http://") or href.startswith("https://"):
|
||||
link_domain = urlparse(href).netloc.lower()
|
||||
if link_domain != domain:
|
||||
a["target"] = "_blank"
|
||||
return soup
|
||||
|
||||
|
||||
def markdown(value, lazy_load=False):
|
||||
extensions = EXTENSIONS
|
||||
html = _markdown.markdown(
|
||||
value, extensions=extensions, extension_configs=EXTENSION_CONFIGS
|
||||
)
|
||||
|
||||
html = bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS)
|
||||
|
||||
if not html:
|
||||
html = escape(value)
|
||||
|
||||
soup = BeautifulSoup(html, features="html.parser")
|
||||
if lazy_load:
|
||||
soup = _wrap_img_iframe_with_lazy_load(soup)
|
||||
|
||||
soup = _wrap_images_with_featherlight(soup)
|
||||
soup = _open_external_links_in_new_tab(soup)
|
||||
html = str(soup)
|
||||
|
||||
return '<div class="md-typeset content-description">%s</div>' % html
|
|
@ -1,2 +0,0 @@
|
|||
from .youtube import YouTubeExtension
|
||||
from .emoticon import EmoticonExtension
|
|
@ -1,112 +0,0 @@
|
|||
import markdown
|
||||
from markdown.extensions import Extension
|
||||
from markdown.inlinepatterns import InlineProcessor
|
||||
import xml.etree.ElementTree as etree
|
||||
import re
|
||||
|
||||
EMOTICON_EMOJI_MAP = {
|
||||
":D": "\U0001F603", # Smiling Face with Open Mouth
|
||||
":)": "\U0001F642", # Slightly Smiling Face
|
||||
":-)": "\U0001F642", # Slightly Smiling Face with Nose
|
||||
":(": "\U0001F641", # Slightly Frowning Face
|
||||
":-(": "\U0001F641", # Slightly Frowning Face with Nose
|
||||
";)": "\U0001F609", # Winking Face
|
||||
";-)": "\U0001F609", # Winking Face with Nose
|
||||
":P": "\U0001F61B", # Face with Tongue
|
||||
":-P": "\U0001F61B", # Face with Tongue and Nose
|
||||
":p": "\U0001F61B", # Face with Tongue
|
||||
":-p": "\U0001F61B", # Face with Tongue and Nose
|
||||
";P": "\U0001F61C", # Winking Face with Tongue
|
||||
";-P": "\U0001F61C", # Winking Face with Tongue and Nose
|
||||
";p": "\U0001F61C", # Winking Face with Tongue
|
||||
";-p": "\U0001F61C", # Winking Face with Tongue and Nose
|
||||
":'(": "\U0001F622", # Crying Face
|
||||
":o": "\U0001F62E", # Face with Open Mouth
|
||||
":-o": "\U0001F62E", # Face with Open Mouth and Nose
|
||||
":O": "\U0001F62E", # Face with Open Mouth
|
||||
":-O": "\U0001F62E", # Face with Open Mouth and Nose
|
||||
":-0": "\U0001F62E", # Face with Open Mouth and Nose
|
||||
">:(": "\U0001F620", # Angry Face
|
||||
">:-(": "\U0001F620", # Angry Face with Nose
|
||||
">:)": "\U0001F608", # Smiling Face with Horns
|
||||
">:-)": "\U0001F608", # Smiling Face with Horns and Nose
|
||||
"XD": "\U0001F606", # Grinning Squinting Face
|
||||
"xD": "\U0001F606", # Grinning Squinting Face
|
||||
"B)": "\U0001F60E", # Smiling Face with Sunglasses
|
||||
"B-)": "\U0001F60E", # Smiling Face with Sunglasses and Nose
|
||||
"O:)": "\U0001F607", # Smiling Face with Halo
|
||||
"O:-)": "\U0001F607", # Smiling Face with Halo and Nose
|
||||
"0:)": "\U0001F607", # Smiling Face with Halo
|
||||
"0:-)": "\U0001F607", # Smiling Face with Halo and Nose
|
||||
">:P": "\U0001F92A", # Zany Face (sticking out tongue and winking)
|
||||
">:-P": "\U0001F92A", # Zany Face with Nose
|
||||
">:p": "\U0001F92A", # Zany Face (sticking out tongue and winking)
|
||||
">:-p": "\U0001F92A", # Zany Face with Nose
|
||||
":/": "\U0001F615", # Confused Face
|
||||
":-/": "\U0001F615", # Confused Face with Nose
|
||||
":\\": "\U0001F615", # Confused Face
|
||||
":-\\": "\U0001F615", # Confused Face with Nose
|
||||
"3:)": "\U0001F608", # Smiling Face with Horns
|
||||
"3:-)": "\U0001F608", # Smiling Face with Horns and Nose
|
||||
"<3": "\u2764\uFE0F", # Red Heart
|
||||
"</3": "\U0001F494", # Broken Heart
|
||||
":*": "\U0001F618", # Face Blowing a Kiss
|
||||
":-*": "\U0001F618", # Face Blowing a Kiss with Nose
|
||||
";P": "\U0001F61C", # Winking Face with Tongue
|
||||
";-P": "\U0001F61C",
|
||||
">:P": "\U0001F61D", # Face with Stuck-Out Tongue and Tightly-Closed Eyes
|
||||
":-/": "\U0001F615", # Confused Face
|
||||
":/": "\U0001F615",
|
||||
":\\": "\U0001F615",
|
||||
":-\\": "\U0001F615",
|
||||
":|": "\U0001F610", # Neutral Face
|
||||
":-|": "\U0001F610",
|
||||
"8)": "\U0001F60E", # Smiling Face with Sunglasses
|
||||
"8-)": "\U0001F60E",
|
||||
"O:)": "\U0001F607", # Smiling Face with Halo
|
||||
"O:-)": "\U0001F607",
|
||||
":3": "\U0001F60A", # Smiling Face with Smiling Eyes
|
||||
"^.^": "\U0001F60A",
|
||||
"-_-": "\U0001F611", # Expressionless Face
|
||||
"T_T": "\U0001F62D", # Loudly Crying Face
|
||||
"T.T": "\U0001F62D",
|
||||
">.<": "\U0001F623", # Persevering Face
|
||||
"x_x": "\U0001F635", # Dizzy Face
|
||||
"X_X": "\U0001F635",
|
||||
":]": "\U0001F600", # Grinning Face
|
||||
":[": "\U0001F641", # Slightly Frowning Face
|
||||
"=]": "\U0001F600",
|
||||
"=[": "\U0001F641",
|
||||
"D:<": "\U0001F621", # Pouting Face
|
||||
"D:": "\U0001F629", # Weary Face
|
||||
"D=": "\U0001F6AB", # No Entry Sign (sometimes used to denote dismay or frustration)
|
||||
":'D": "\U0001F602", # Face with Tears of Joy
|
||||
"D':": "\U0001F625", # Disappointed but Relieved Face
|
||||
"D8": "\U0001F631", # Face Screaming in Fear
|
||||
"-.-": "\U0001F644", # Face with Rolling Eyes
|
||||
"-_-;": "\U0001F612", # Unamused
|
||||
}
|
||||
|
||||
|
||||
class EmoticonEmojiInlineProcessor(InlineProcessor):
|
||||
def handleMatch(self, m, data):
|
||||
emoticon = m.group(1)
|
||||
emoji = EMOTICON_EMOJI_MAP.get(emoticon, "")
|
||||
if emoji:
|
||||
el = etree.Element("span")
|
||||
el.text = markdown.util.AtomicString(emoji)
|
||||
el.set("class", "big-emoji")
|
||||
return el, m.start(0), m.end(0)
|
||||
else:
|
||||
return None, m.start(0), m.end(0)
|
||||
|
||||
|
||||
class EmoticonExtension(Extension):
|
||||
def extendMarkdown(self, md):
|
||||
emoticon_pattern = (
|
||||
r"(?:(?<=\s)|^)" # Lookbehind for a whitespace character or the start of the string
|
||||
r"(" + "|".join(map(re.escape, EMOTICON_EMOJI_MAP.keys())) + r")"
|
||||
r"(?=\s|$)" # Lookahead for a whitespace character or the end of the string
|
||||
)
|
||||
emoticon_processor = EmoticonEmojiInlineProcessor(emoticon_pattern, md)
|
||||
md.inlinePatterns.register(emoticon_processor, "emoticon_to_emoji", 1)
|
|
@ -1,36 +0,0 @@
|
|||
import markdown
|
||||
from markdown.inlinepatterns import InlineProcessor
|
||||
from markdown.extensions import Extension
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
YOUTUBE_REGEX = (
|
||||
r"(https?://)?(www\.)?" "(youtube\.com/watch\?v=|youtu\.be/)" "([\w-]+)(&[\w=]*)?"
|
||||
)
|
||||
|
||||
|
||||
class YouTubeEmbedProcessor(InlineProcessor):
|
||||
def handleMatch(self, m, data):
|
||||
youtube_id = m.group(4)
|
||||
if not youtube_id:
|
||||
return None, None, None
|
||||
|
||||
# Create an iframe element with the YouTube embed URL
|
||||
iframe = etree.Element("iframe")
|
||||
iframe.set("width", "100%")
|
||||
iframe.set("height", "360")
|
||||
iframe.set("src", f"https://www.youtube.com/embed/{youtube_id}")
|
||||
iframe.set("frameborder", "0")
|
||||
iframe.set("allowfullscreen", "true")
|
||||
center = etree.Element("center")
|
||||
center.append(iframe)
|
||||
|
||||
# Return the iframe as the element to replace the match, along with the start and end indices
|
||||
return center, m.start(0), m.end(0)
|
||||
|
||||
|
||||
class YouTubeExtension(Extension):
|
||||
def extendMarkdown(self, md):
|
||||
# Create the YouTube link pattern
|
||||
YOUTUBE_PATTERN = YouTubeEmbedProcessor(YOUTUBE_REGEX, md)
|
||||
# Register the pattern to apply the YouTubeEmbedProcessor
|
||||
md.inlinePatterns.register(YOUTUBE_PATTERN, "youtube", 175)
|
|
@ -1,23 +1,7 @@
|
|||
import time
|
||||
import logging
|
||||
import random
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect, Http404
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import Resolver404, resolve, reverse
|
||||
from django.utils.http import urlquote
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from judge.models import Organization
|
||||
from judge.utils.views import generic_message
|
||||
|
||||
|
||||
USED_DOMAINS = ["www"]
|
||||
URL_NAMES_BYPASS_SUBDOMAIN = ["submission_source_file"]
|
||||
|
||||
|
||||
class ShortCircuitMiddleware:
|
||||
|
@ -86,95 +70,3 @@ class ContestMiddleware(object):
|
|||
request.participation = None
|
||||
request.in_contest_mode = request.in_contest and request.contest_mode
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
class DarkModeMiddleware(object):
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
if "darkmode" in request.GET:
|
||||
return HttpResponseRedirect(
|
||||
reverse("toggle_darkmode") + "?next=" + urlquote(request.path)
|
||||
)
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
class SubdomainMiddleware(object):
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
request.organization = None
|
||||
if not settings.USE_SUBDOMAIN:
|
||||
return self.get_response(request)
|
||||
|
||||
domain = request.get_host()
|
||||
site = get_current_site(request).domain
|
||||
subdomain = domain[: len(domain) - len(site)].lower()
|
||||
|
||||
if len(subdomain) <= 1:
|
||||
return self.get_response(request)
|
||||
|
||||
subdomain = subdomain[:-1]
|
||||
|
||||
if (
|
||||
subdomain in USED_DOMAINS
|
||||
or resolve(request.path).url_name in URL_NAMES_BYPASS_SUBDOMAIN
|
||||
):
|
||||
return self.get_response(request)
|
||||
|
||||
try:
|
||||
organization = Organization.objects.get(slug=subdomain)
|
||||
if request.profile and organization in request.profile.organizations.all():
|
||||
request.organization = organization
|
||||
else:
|
||||
if request.profile:
|
||||
return generic_message(
|
||||
request,
|
||||
_("No permission"),
|
||||
_("You need to join this group first"),
|
||||
status=404,
|
||||
)
|
||||
if not request.GET.get("next", None):
|
||||
return HttpResponseRedirect(
|
||||
reverse("auth_login") + "?next=" + urlquote(request.path)
|
||||
)
|
||||
except ObjectDoesNotExist:
|
||||
return generic_message(
|
||||
request,
|
||||
_("No such group"),
|
||||
_("No such group"),
|
||||
status=404,
|
||||
)
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
class SlowRequestMiddleware(object):
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
logger = logging.getLogger("judge.request_time")
|
||||
logger_slow = logging.getLogger("judge.slow_request")
|
||||
start_time = time.time()
|
||||
response = self.get_response(request)
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
response_time = time.time() - start_time
|
||||
url_name = resolve(request.path).url_name
|
||||
message = {
|
||||
"url_name": url_name,
|
||||
"response_time": response_time * 1000,
|
||||
"profile": request.user.username,
|
||||
"date": datetime.now().strftime("%Y/%m/%d"),
|
||||
"url": request.build_absolute_uri(),
|
||||
"method": request.method,
|
||||
}
|
||||
if response_time > 9:
|
||||
logger_slow.info(json.dumps(message))
|
||||
if random.random() < 0.1:
|
||||
logger.info(json.dumps(message))
|
||||
except Exception:
|
||||
pass
|
||||
return response
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import lxml.html as lh
|
||||
from django.db import migrations
|
||||
from lxml_html_clean import clean_html
|
||||
from lxml.html.clean import clean_html
|
||||
|
||||
|
||||
def strip_error_html(apps, schema_editor):
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-06-12 06:59
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0125_auto_20220602_1216"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="LanguageTemplate",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"source",
|
||||
models.TextField(max_length=65536, verbose_name="source code"),
|
||||
),
|
||||
(
|
||||
"language",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="judge.Language",
|
||||
verbose_name="language",
|
||||
),
|
||||
),
|
||||
(
|
||||
"problem",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="language_templates",
|
||||
to="judge.Problem",
|
||||
verbose_name="problem",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "language-specific template",
|
||||
"verbose_name_plural": "language-specific templates",
|
||||
"unique_together": {("problem", "language")},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,29 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-06-16 07:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0126_languagetemplate"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="submission",
|
||||
options={
|
||||
"permissions": (
|
||||
("abort_any_submission", "Abort any submission"),
|
||||
("rejudge_submission", "Rejudge the submission"),
|
||||
("rejudge_submission_lot", "Rejudge a lot of submissions"),
|
||||
("spam_submission", "Submit without limit"),
|
||||
("view_all_submission", "View all submission"),
|
||||
("resubmit_other", "Resubmit others' submission"),
|
||||
("view_public_submission", "View public submissions"),
|
||||
),
|
||||
"verbose_name": "submission",
|
||||
"verbose_name_plural": "submissions",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,26 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-06-20 15:10
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0127_auto_20220616_1442"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="problem",
|
||||
name="memory_limit",
|
||||
field=models.PositiveIntegerField(
|
||||
help_text="The memory limit for this problem, in kilobytes (e.g. 256mb = 262144 kilobytes).",
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(1048576),
|
||||
],
|
||||
verbose_name="memory limit",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,59 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-06-22 07:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0128_auto_20220620_2210"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="problemdata",
|
||||
name="checker",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("standard", "Standard"),
|
||||
("floats", "Floats"),
|
||||
("floatsabs", "Floats (absolute)"),
|
||||
("floatsrel", "Floats (relative)"),
|
||||
("rstripped", "Non-trailing spaces"),
|
||||
("sorted", "Unordered"),
|
||||
("identical", "Byte identical"),
|
||||
("linecount", "Line-by-line"),
|
||||
("custom", "Custom checker (PY)"),
|
||||
("customval", "Custom validator (CPP)"),
|
||||
("interact", "Interactive"),
|
||||
("testlib", "Testlib"),
|
||||
],
|
||||
max_length=10,
|
||||
verbose_name="checker",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="problemtestcase",
|
||||
name="checker",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("standard", "Standard"),
|
||||
("floats", "Floats"),
|
||||
("floatsabs", "Floats (absolute)"),
|
||||
("floatsrel", "Floats (relative)"),
|
||||
("rstripped", "Non-trailing spaces"),
|
||||
("sorted", "Unordered"),
|
||||
("identical", "Byte identical"),
|
||||
("linecount", "Line-by-line"),
|
||||
("custom", "Custom checker (PY)"),
|
||||
("customval", "Custom validator (CPP)"),
|
||||
("interact", "Interactive"),
|
||||
("testlib", "Testlib"),
|
||||
],
|
||||
max_length=10,
|
||||
verbose_name="checker",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,31 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-08-31 03:48
|
||||
|
||||
from django.db import migrations, models
|
||||
import judge.models.problem
|
||||
import judge.utils.problem_data
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0129_auto_20220622_1424"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="problem",
|
||||
name="pdf_description",
|
||||
field=models.FileField(
|
||||
blank=True,
|
||||
null=True,
|
||||
storage=judge.utils.problem_data.ProblemDataStorage(),
|
||||
upload_to=judge.models.problem.problem_directory_file,
|
||||
verbose_name="pdf statement",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="problem",
|
||||
name="description",
|
||||
field=models.TextField(blank=True, verbose_name="problem body"),
|
||||
),
|
||||
]
|
|
@ -1,27 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-09-04 17:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0130_auto_20220831_1048"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="category",
|
||||
field=models.CharField(max_length=50, verbose_name="category"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="problemtranslation",
|
||||
name="language",
|
||||
field=models.CharField(
|
||||
choices=[("en", "English"), ("vi", "Vietnamese")],
|
||||
max_length=7,
|
||||
verbose_name="language",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,28 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-09-15 06:49
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0131_auto_20220905_0027"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="contestproblem",
|
||||
name="max_submissions",
|
||||
field=models.IntegerField(
|
||||
default=0,
|
||||
help_text="Maximum number of submissions for this problem, or 0 for no limit.",
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(
|
||||
0, "Why include a problem you can't submit to?"
|
||||
)
|
||||
],
|
||||
verbose_name="max submissions",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,46 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-10-13 01:50
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0132_auto_20220915_1349"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ContestProblemClarification",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("description", models.TextField(verbose_name="clarification body")),
|
||||
(
|
||||
"date",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="clarification timestamp"
|
||||
),
|
||||
),
|
||||
(
|
||||
"problem",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="judge.ContestProblem",
|
||||
verbose_name="clarified problem",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="ProblemClarification",
|
||||
),
|
||||
]
|
|
@ -1,707 +0,0 @@
|
|||
# Generated by Django 2.2.28 on 2022-10-17 18:24
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0133_auto_20221013_0850"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="profile",
|
||||
name="timezone",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
(
|
||||
"Africa",
|
||||
[
|
||||
("Africa/Abidjan", "Abidjan"),
|
||||
("Africa/Accra", "Accra"),
|
||||
("Africa/Addis_Ababa", "Addis_Ababa"),
|
||||
("Africa/Algiers", "Algiers"),
|
||||
("Africa/Asmara", "Asmara"),
|
||||
("Africa/Asmera", "Asmera"),
|
||||
("Africa/Bamako", "Bamako"),
|
||||
("Africa/Bangui", "Bangui"),
|
||||
("Africa/Banjul", "Banjul"),
|
||||
("Africa/Bissau", "Bissau"),
|
||||
("Africa/Blantyre", "Blantyre"),
|
||||
("Africa/Brazzaville", "Brazzaville"),
|
||||
("Africa/Bujumbura", "Bujumbura"),
|
||||
("Africa/Cairo", "Cairo"),
|
||||
("Africa/Casablanca", "Casablanca"),
|
||||
("Africa/Ceuta", "Ceuta"),
|
||||
("Africa/Conakry", "Conakry"),
|
||||
("Africa/Dakar", "Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Djibouti"),
|
||||
("Africa/Douala", "Douala"),
|
||||
("Africa/El_Aaiun", "El_Aaiun"),
|
||||
("Africa/Freetown", "Freetown"),
|
||||
("Africa/Gaborone", "Gaborone"),
|
||||
("Africa/Harare", "Harare"),
|
||||
("Africa/Johannesburg", "Johannesburg"),
|
||||
("Africa/Juba", "Juba"),
|
||||
("Africa/Kampala", "Kampala"),
|
||||
("Africa/Khartoum", "Khartoum"),
|
||||
("Africa/Kigali", "Kigali"),
|
||||
("Africa/Kinshasa", "Kinshasa"),
|
||||
("Africa/Lagos", "Lagos"),
|
||||
("Africa/Libreville", "Libreville"),
|
||||
("Africa/Lome", "Lome"),
|
||||
("Africa/Luanda", "Luanda"),
|
||||
("Africa/Lubumbashi", "Lubumbashi"),
|
||||
("Africa/Lusaka", "Lusaka"),
|
||||
("Africa/Malabo", "Malabo"),
|
||||
("Africa/Maputo", "Maputo"),
|
||||
("Africa/Maseru", "Maseru"),
|
||||
("Africa/Mbabane", "Mbabane"),
|
||||
("Africa/Mogadishu", "Mogadishu"),
|
||||
("Africa/Monrovia", "Monrovia"),
|
||||
("Africa/Nairobi", "Nairobi"),
|
||||
("Africa/Ndjamena", "Ndjamena"),
|
||||
("Africa/Niamey", "Niamey"),
|
||||
("Africa/Nouakchott", "Nouakchott"),
|
||||
("Africa/Ouagadougou", "Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Sao_Tome"),
|
||||
("Africa/Timbuktu", "Timbuktu"),
|
||||
("Africa/Tripoli", "Tripoli"),
|
||||
("Africa/Tunis", "Tunis"),
|
||||
("Africa/Windhoek", "Windhoek"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"America",
|
||||
[
|
||||
("America/Adak", "Adak"),
|
||||
("America/Anchorage", "Anchorage"),
|
||||
("America/Anguilla", "Anguilla"),
|
||||
("America/Antigua", "Antigua"),
|
||||
("America/Araguaina", "Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "Argentina/Catamarca"),
|
||||
(
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"Argentina/ComodRivadavia",
|
||||
),
|
||||
("America/Argentina/Cordoba", "Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "Argentina/Ushuaia"),
|
||||
("America/Aruba", "Aruba"),
|
||||
("America/Asuncion", "Asuncion"),
|
||||
("America/Atikokan", "Atikokan"),
|
||||
("America/Atka", "Atka"),
|
||||
("America/Bahia", "Bahia"),
|
||||
("America/Bahia_Banderas", "Bahia_Banderas"),
|
||||
("America/Barbados", "Barbados"),
|
||||
("America/Belem", "Belem"),
|
||||
("America/Belize", "Belize"),
|
||||
("America/Blanc-Sablon", "Blanc-Sablon"),
|
||||
("America/Boa_Vista", "Boa_Vista"),
|
||||
("America/Bogota", "Bogota"),
|
||||
("America/Boise", "Boise"),
|
||||
("America/Buenos_Aires", "Buenos_Aires"),
|
||||
("America/Cambridge_Bay", "Cambridge_Bay"),
|
||||
("America/Campo_Grande", "Campo_Grande"),
|
||||
("America/Cancun", "Cancun"),
|
||||
("America/Caracas", "Caracas"),
|
||||
("America/Catamarca", "Catamarca"),
|
||||
("America/Cayenne", "Cayenne"),
|
||||
("America/Cayman", "Cayman"),
|
||||
("America/Chicago", "Chicago"),
|
||||
("America/Chihuahua", "Chihuahua"),
|
||||
("America/Coral_Harbour", "Coral_Harbour"),
|
||||
("America/Cordoba", "Cordoba"),
|
||||
("America/Costa_Rica", "Costa_Rica"),
|
||||
("America/Creston", "Creston"),
|
||||
("America/Cuiaba", "Cuiaba"),
|
||||
("America/Curacao", "Curacao"),
|
||||
("America/Danmarkshavn", "Danmarkshavn"),
|
||||
("America/Dawson", "Dawson"),
|
||||
("America/Dawson_Creek", "Dawson_Creek"),
|
||||
("America/Denver", "Denver"),
|
||||
("America/Detroit", "Detroit"),
|
||||
("America/Dominica", "Dominica"),
|
||||
("America/Edmonton", "Edmonton"),
|
||||
("America/Eirunepe", "Eirunepe"),
|
||||
("America/El_Salvador", "El_Salvador"),
|
||||
("America/Ensenada", "Ensenada"),
|
||||
("America/Fort_Nelson", "Fort_Nelson"),
|
||||
("America/Fort_Wayne", "Fort_Wayne"),
|
||||
("America/Fortaleza", "Fortaleza"),
|
||||
("America/Glace_Bay", "Glace_Bay"),
|
||||
("America/Godthab", "Godthab"),
|
||||
("America/Goose_Bay", "Goose_Bay"),
|
||||
("America/Grand_Turk", "Grand_Turk"),
|
||||
("America/Grenada", "Grenada"),
|
||||
("America/Guadeloupe", "Guadeloupe"),
|
||||
("America/Guatemala", "Guatemala"),
|
||||
("America/Guayaquil", "Guayaquil"),
|
||||
("America/Guyana", "Guyana"),
|
||||
("America/Halifax", "Halifax"),
|
||||
("America/Havana", "Havana"),
|
||||
("America/Hermosillo", "Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "Indiana/Winamac"),
|
||||
("America/Indianapolis", "Indianapolis"),
|
||||
("America/Inuvik", "Inuvik"),
|
||||
("America/Iqaluit", "Iqaluit"),
|
||||
("America/Jamaica", "Jamaica"),
|
||||
("America/Jujuy", "Jujuy"),
|
||||
("America/Juneau", "Juneau"),
|
||||
("America/Kentucky/Louisville", "Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "Kentucky/Monticello"),
|
||||
("America/Knox_IN", "Knox_IN"),
|
||||
("America/Kralendijk", "Kralendijk"),
|
||||
("America/La_Paz", "La_Paz"),
|
||||
("America/Lima", "Lima"),
|
||||
("America/Los_Angeles", "Los_Angeles"),
|
||||
("America/Louisville", "Louisville"),
|
||||
("America/Lower_Princes", "Lower_Princes"),
|
||||
("America/Maceio", "Maceio"),
|
||||
("America/Managua", "Managua"),
|
||||
("America/Manaus", "Manaus"),
|
||||
("America/Marigot", "Marigot"),
|
||||
("America/Martinique", "Martinique"),
|
||||
("America/Matamoros", "Matamoros"),
|
||||
("America/Mazatlan", "Mazatlan"),
|
||||
("America/Mendoza", "Mendoza"),
|
||||
("America/Menominee", "Menominee"),
|
||||
("America/Merida", "Merida"),
|
||||
("America/Metlakatla", "Metlakatla"),
|
||||
("America/Mexico_City", "Mexico_City"),
|
||||
("America/Miquelon", "Miquelon"),
|
||||
("America/Moncton", "Moncton"),
|
||||
("America/Monterrey", "Monterrey"),
|
||||
("America/Montevideo", "Montevideo"),
|
||||
("America/Montreal", "Montreal"),
|
||||
("America/Montserrat", "Montserrat"),
|
||||
("America/Nassau", "Nassau"),
|
||||
("America/New_York", "New_York"),
|
||||
("America/Nipigon", "Nipigon"),
|
||||
("America/Nome", "Nome"),
|
||||
("America/Noronha", "Noronha"),
|
||||
("America/North_Dakota/Beulah", "North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "Nuuk"),
|
||||
("America/Ojinaga", "Ojinaga"),
|
||||
("America/Panama", "Panama"),
|
||||
("America/Pangnirtung", "Pangnirtung"),
|
||||
("America/Paramaribo", "Paramaribo"),
|
||||
("America/Phoenix", "Phoenix"),
|
||||
("America/Port-au-Prince", "Port-au-Prince"),
|
||||
("America/Port_of_Spain", "Port_of_Spain"),
|
||||
("America/Porto_Acre", "Porto_Acre"),
|
||||
("America/Porto_Velho", "Porto_Velho"),
|
||||
("America/Puerto_Rico", "Puerto_Rico"),
|
||||
("America/Punta_Arenas", "Punta_Arenas"),
|
||||
("America/Rainy_River", "Rainy_River"),
|
||||
("America/Rankin_Inlet", "Rankin_Inlet"),
|
||||
("America/Recife", "Recife"),
|
||||
("America/Regina", "Regina"),
|
||||
("America/Resolute", "Resolute"),
|
||||
("America/Rio_Branco", "Rio_Branco"),
|
||||
("America/Rosario", "Rosario"),
|
||||
("America/Santa_Isabel", "Santa_Isabel"),
|
||||
("America/Santarem", "Santarem"),
|
||||
("America/Santiago", "Santiago"),
|
||||
("America/Santo_Domingo", "Santo_Domingo"),
|
||||
("America/Sao_Paulo", "Sao_Paulo"),
|
||||
("America/Scoresbysund", "Scoresbysund"),
|
||||
("America/Shiprock", "Shiprock"),
|
||||
("America/Sitka", "Sitka"),
|
||||
("America/St_Barthelemy", "St_Barthelemy"),
|
||||
("America/St_Johns", "St_Johns"),
|
||||
("America/St_Kitts", "St_Kitts"),
|
||||
("America/St_Lucia", "St_Lucia"),
|
||||
("America/St_Thomas", "St_Thomas"),
|
||||
("America/St_Vincent", "St_Vincent"),
|
||||
("America/Swift_Current", "Swift_Current"),
|
||||
("America/Tegucigalpa", "Tegucigalpa"),
|
||||
("America/Thule", "Thule"),
|
||||
("America/Thunder_Bay", "Thunder_Bay"),
|
||||
("America/Tijuana", "Tijuana"),
|
||||
("America/Toronto", "Toronto"),
|
||||
("America/Tortola", "Tortola"),
|
||||
("America/Vancouver", "Vancouver"),
|
||||
("America/Virgin", "Virgin"),
|
||||
("America/Whitehorse", "Whitehorse"),
|
||||
("America/Winnipeg", "Winnipeg"),
|
||||
("America/Yakutat", "Yakutat"),
|
||||
("America/Yellowknife", "Yellowknife"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Antarctica",
|
||||
[
|
||||
("Antarctica/Casey", "Casey"),
|
||||
("Antarctica/Davis", "Davis"),
|
||||
("Antarctica/DumontDUrville", "DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Macquarie"),
|
||||
("Antarctica/Mawson", "Mawson"),
|
||||
("Antarctica/McMurdo", "McMurdo"),
|
||||
("Antarctica/Palmer", "Palmer"),
|
||||
("Antarctica/Rothera", "Rothera"),
|
||||
("Antarctica/South_Pole", "South_Pole"),
|
||||
("Antarctica/Syowa", "Syowa"),
|
||||
("Antarctica/Troll", "Troll"),
|
||||
("Antarctica/Vostok", "Vostok"),
|
||||
],
|
||||
),
|
||||
("Arctic", [("Arctic/Longyearbyen", "Longyearbyen")]),
|
||||
(
|
||||
"Asia",
|
||||
[
|
||||
("Asia/Aden", "Aden"),
|
||||
("Asia/Almaty", "Almaty"),
|
||||
("Asia/Amman", "Amman"),
|
||||
("Asia/Anadyr", "Anadyr"),
|
||||
("Asia/Aqtau", "Aqtau"),
|
||||
("Asia/Aqtobe", "Aqtobe"),
|
||||
("Asia/Ashgabat", "Ashgabat"),
|
||||
("Asia/Ashkhabad", "Ashkhabad"),
|
||||
("Asia/Atyrau", "Atyrau"),
|
||||
("Asia/Baghdad", "Baghdad"),
|
||||
("Asia/Bahrain", "Bahrain"),
|
||||
("Asia/Baku", "Baku"),
|
||||
("Asia/Bangkok", "Bangkok"),
|
||||
("Asia/Barnaul", "Barnaul"),
|
||||
("Asia/Beirut", "Beirut"),
|
||||
("Asia/Bishkek", "Bishkek"),
|
||||
("Asia/Brunei", "Brunei"),
|
||||
("Asia/Calcutta", "Calcutta"),
|
||||
("Asia/Chita", "Chita"),
|
||||
("Asia/Choibalsan", "Choibalsan"),
|
||||
("Asia/Chongqing", "Chongqing"),
|
||||
("Asia/Chungking", "Chungking"),
|
||||
("Asia/Colombo", "Colombo"),
|
||||
("Asia/Dacca", "Dacca"),
|
||||
("Asia/Damascus", "Damascus"),
|
||||
("Asia/Dhaka", "Dhaka"),
|
||||
("Asia/Dili", "Dili"),
|
||||
("Asia/Dubai", "Dubai"),
|
||||
("Asia/Dushanbe", "Dushanbe"),
|
||||
("Asia/Famagusta", "Famagusta"),
|
||||
("Asia/Gaza", "Gaza"),
|
||||
("Asia/Harbin", "Harbin"),
|
||||
("Asia/Hebron", "Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Hong_Kong"),
|
||||
("Asia/Hovd", "Hovd"),
|
||||
("Asia/Irkutsk", "Irkutsk"),
|
||||
("Asia/Istanbul", "Istanbul"),
|
||||
("Asia/Jakarta", "Jakarta"),
|
||||
("Asia/Jayapura", "Jayapura"),
|
||||
("Asia/Jerusalem", "Jerusalem"),
|
||||
("Asia/Kabul", "Kabul"),
|
||||
("Asia/Kamchatka", "Kamchatka"),
|
||||
("Asia/Karachi", "Karachi"),
|
||||
("Asia/Kashgar", "Kashgar"),
|
||||
("Asia/Kathmandu", "Kathmandu"),
|
||||
("Asia/Katmandu", "Katmandu"),
|
||||
("Asia/Khandyga", "Khandyga"),
|
||||
("Asia/Kolkata", "Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Kuching"),
|
||||
("Asia/Kuwait", "Kuwait"),
|
||||
("Asia/Macao", "Macao"),
|
||||
("Asia/Macau", "Macau"),
|
||||
("Asia/Magadan", "Magadan"),
|
||||
("Asia/Makassar", "Makassar"),
|
||||
("Asia/Manila", "Manila"),
|
||||
("Asia/Muscat", "Muscat"),
|
||||
("Asia/Nicosia", "Nicosia"),
|
||||
("Asia/Novokuznetsk", "Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Novosibirsk"),
|
||||
("Asia/Omsk", "Omsk"),
|
||||
("Asia/Oral", "Oral"),
|
||||
("Asia/Phnom_Penh", "Phnom_Penh"),
|
||||
("Asia/Pontianak", "Pontianak"),
|
||||
("Asia/Pyongyang", "Pyongyang"),
|
||||
("Asia/Qatar", "Qatar"),
|
||||
("Asia/Qostanay", "Qostanay"),
|
||||
("Asia/Qyzylorda", "Qyzylorda"),
|
||||
("Asia/Rangoon", "Rangoon"),
|
||||
("Asia/Riyadh", "Riyadh"),
|
||||
("Asia/Saigon", "Saigon"),
|
||||
("Asia/Sakhalin", "Sakhalin"),
|
||||
("Asia/Samarkand", "Samarkand"),
|
||||
("Asia/Seoul", "Seoul"),
|
||||
("Asia/Shanghai", "Shanghai"),
|
||||
("Asia/Singapore", "Singapore"),
|
||||
("Asia/Srednekolymsk", "Srednekolymsk"),
|
||||
("Asia/Taipei", "Taipei"),
|
||||
("Asia/Tashkent", "Tashkent"),
|
||||
("Asia/Tbilisi", "Tbilisi"),
|
||||
("Asia/Tehran", "Tehran"),
|
||||
("Asia/Tel_Aviv", "Tel_Aviv"),
|
||||
("Asia/Thimbu", "Thimbu"),
|
||||
("Asia/Thimphu", "Thimphu"),
|
||||
("Asia/Tokyo", "Tokyo"),
|
||||
("Asia/Tomsk", "Tomsk"),
|
||||
("Asia/Ujung_Pandang", "Ujung_Pandang"),
|
||||
("Asia/Ulaanbaatar", "Ulaanbaatar"),
|
||||
("Asia/Ulan_Bator", "Ulan_Bator"),
|
||||
("Asia/Urumqi", "Urumqi"),
|
||||
("Asia/Ust-Nera", "Ust-Nera"),
|
||||
("Asia/Vientiane", "Vientiane"),
|
||||
("Asia/Vladivostok", "Vladivostok"),
|
||||
("Asia/Yakutsk", "Yakutsk"),
|
||||
("Asia/Yangon", "Yangon"),
|
||||
("Asia/Yekaterinburg", "Yekaterinburg"),
|
||||
("Asia/Yerevan", "Yerevan"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Atlantic",
|
||||
[
|
||||
("Atlantic/Azores", "Azores"),
|
||||
("Atlantic/Bermuda", "Bermuda"),
|
||||
("Atlantic/Canary", "Canary"),
|
||||
("Atlantic/Cape_Verde", "Cape_Verde"),
|
||||
("Atlantic/Faeroe", "Faeroe"),
|
||||
("Atlantic/Faroe", "Faroe"),
|
||||
("Atlantic/Jan_Mayen", "Jan_Mayen"),
|
||||
("Atlantic/Madeira", "Madeira"),
|
||||
("Atlantic/Reykjavik", "Reykjavik"),
|
||||
("Atlantic/South_Georgia", "South_Georgia"),
|
||||
("Atlantic/St_Helena", "St_Helena"),
|
||||
("Atlantic/Stanley", "Stanley"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Australia",
|
||||
[
|
||||
("Australia/ACT", "ACT"),
|
||||
("Australia/Adelaide", "Adelaide"),
|
||||
("Australia/Brisbane", "Brisbane"),
|
||||
("Australia/Broken_Hill", "Broken_Hill"),
|
||||
("Australia/Canberra", "Canberra"),
|
||||
("Australia/Currie", "Currie"),
|
||||
("Australia/Darwin", "Darwin"),
|
||||
("Australia/Eucla", "Eucla"),
|
||||
("Australia/Hobart", "Hobart"),
|
||||
("Australia/LHI", "LHI"),
|
||||
("Australia/Lindeman", "Lindeman"),
|
||||
("Australia/Lord_Howe", "Lord_Howe"),
|
||||
("Australia/Melbourne", "Melbourne"),
|
||||
("Australia/NSW", "NSW"),
|
||||
("Australia/North", "North"),
|
||||
("Australia/Perth", "Perth"),
|
||||
("Australia/Queensland", "Queensland"),
|
||||
("Australia/South", "South"),
|
||||
("Australia/Sydney", "Sydney"),
|
||||
("Australia/Tasmania", "Tasmania"),
|
||||
("Australia/Victoria", "Victoria"),
|
||||
("Australia/West", "West"),
|
||||
("Australia/Yancowinna", "Yancowinna"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Brazil",
|
||||
[
|
||||
("Brazil/Acre", "Acre"),
|
||||
("Brazil/DeNoronha", "DeNoronha"),
|
||||
("Brazil/East", "East"),
|
||||
("Brazil/West", "West"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Canada",
|
||||
[
|
||||
("Canada/Atlantic", "Atlantic"),
|
||||
("Canada/Central", "Central"),
|
||||
("Canada/Eastern", "Eastern"),
|
||||
("Canada/Mountain", "Mountain"),
|
||||
("Canada/Newfoundland", "Newfoundland"),
|
||||
("Canada/Pacific", "Pacific"),
|
||||
("Canada/Saskatchewan", "Saskatchewan"),
|
||||
("Canada/Yukon", "Yukon"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Chile",
|
||||
[
|
||||
("Chile/Continental", "Continental"),
|
||||
("Chile/EasterIsland", "EasterIsland"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Etc",
|
||||
[
|
||||
("Etc/Greenwich", "Greenwich"),
|
||||
("Etc/UCT", "UCT"),
|
||||
("Etc/UTC", "UTC"),
|
||||
("Etc/Universal", "Universal"),
|
||||
("Etc/Zulu", "Zulu"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Europe",
|
||||
[
|
||||
("Europe/Amsterdam", "Amsterdam"),
|
||||
("Europe/Andorra", "Andorra"),
|
||||
("Europe/Astrakhan", "Astrakhan"),
|
||||
("Europe/Athens", "Athens"),
|
||||
("Europe/Belfast", "Belfast"),
|
||||
("Europe/Belgrade", "Belgrade"),
|
||||
("Europe/Berlin", "Berlin"),
|
||||
("Europe/Bratislava", "Bratislava"),
|
||||
("Europe/Brussels", "Brussels"),
|
||||
("Europe/Bucharest", "Bucharest"),
|
||||
("Europe/Budapest", "Budapest"),
|
||||
("Europe/Busingen", "Busingen"),
|
||||
("Europe/Chisinau", "Chisinau"),
|
||||
("Europe/Copenhagen", "Copenhagen"),
|
||||
("Europe/Dublin", "Dublin"),
|
||||
("Europe/Gibraltar", "Gibraltar"),
|
||||
("Europe/Guernsey", "Guernsey"),
|
||||
("Europe/Helsinki", "Helsinki"),
|
||||
("Europe/Isle_of_Man", "Isle_of_Man"),
|
||||
("Europe/Istanbul", "Istanbul"),
|
||||
("Europe/Jersey", "Jersey"),
|
||||
("Europe/Kaliningrad", "Kaliningrad"),
|
||||
("Europe/Kiev", "Kiev"),
|
||||
("Europe/Kirov", "Kirov"),
|
||||
("Europe/Kyiv", "Kyiv"),
|
||||
("Europe/Lisbon", "Lisbon"),
|
||||
("Europe/Ljubljana", "Ljubljana"),
|
||||
("Europe/London", "London"),
|
||||
("Europe/Luxembourg", "Luxembourg"),
|
||||
("Europe/Madrid", "Madrid"),
|
||||
("Europe/Malta", "Malta"),
|
||||
("Europe/Mariehamn", "Mariehamn"),
|
||||
("Europe/Minsk", "Minsk"),
|
||||
("Europe/Monaco", "Monaco"),
|
||||
("Europe/Moscow", "Moscow"),
|
||||
("Europe/Nicosia", "Nicosia"),
|
||||
("Europe/Oslo", "Oslo"),
|
||||
("Europe/Paris", "Paris"),
|
||||
("Europe/Podgorica", "Podgorica"),
|
||||
("Europe/Prague", "Prague"),
|
||||
("Europe/Riga", "Riga"),
|
||||
("Europe/Rome", "Rome"),
|
||||
("Europe/Samara", "Samara"),
|
||||
("Europe/San_Marino", "San_Marino"),
|
||||
("Europe/Sarajevo", "Sarajevo"),
|
||||
("Europe/Saratov", "Saratov"),
|
||||
("Europe/Simferopol", "Simferopol"),
|
||||
("Europe/Skopje", "Skopje"),
|
||||
("Europe/Sofia", "Sofia"),
|
||||
("Europe/Stockholm", "Stockholm"),
|
||||
("Europe/Tallinn", "Tallinn"),
|
||||
("Europe/Tirane", "Tirane"),
|
||||
("Europe/Tiraspol", "Tiraspol"),
|
||||
("Europe/Ulyanovsk", "Ulyanovsk"),
|
||||
("Europe/Uzhgorod", "Uzhgorod"),
|
||||
("Europe/Vaduz", "Vaduz"),
|
||||
("Europe/Vatican", "Vatican"),
|
||||
("Europe/Vienna", "Vienna"),
|
||||
("Europe/Vilnius", "Vilnius"),
|
||||
("Europe/Volgograd", "Volgograd"),
|
||||
("Europe/Warsaw", "Warsaw"),
|
||||
("Europe/Zagreb", "Zagreb"),
|
||||
("Europe/Zaporozhye", "Zaporozhye"),
|
||||
("Europe/Zurich", "Zurich"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Indian",
|
||||
[
|
||||
("Indian/Antananarivo", "Antananarivo"),
|
||||
("Indian/Chagos", "Chagos"),
|
||||
("Indian/Christmas", "Christmas"),
|
||||
("Indian/Cocos", "Cocos"),
|
||||
("Indian/Comoro", "Comoro"),
|
||||
("Indian/Kerguelen", "Kerguelen"),
|
||||
("Indian/Mahe", "Mahe"),
|
||||
("Indian/Maldives", "Maldives"),
|
||||
("Indian/Mauritius", "Mauritius"),
|
||||
("Indian/Mayotte", "Mayotte"),
|
||||
("Indian/Reunion", "Reunion"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Mexico",
|
||||
[
|
||||
("Mexico/BajaNorte", "BajaNorte"),
|
||||
("Mexico/BajaSur", "BajaSur"),
|
||||
("Mexico/General", "General"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Other",
|
||||
[
|
||||
("CET", "CET"),
|
||||
("CST6CDT", "CST6CDT"),
|
||||
("Cuba", "Cuba"),
|
||||
("EET", "EET"),
|
||||
("EST", "EST"),
|
||||
("EST5EDT", "EST5EDT"),
|
||||
("Egypt", "Egypt"),
|
||||
("Eire", "Eire"),
|
||||
("GB", "GB"),
|
||||
("GB-Eire", "GB-Eire"),
|
||||
("Greenwich", "Greenwich"),
|
||||
("HST", "HST"),
|
||||
("Hongkong", "Hongkong"),
|
||||
("Iceland", "Iceland"),
|
||||
("Iran", "Iran"),
|
||||
("Israel", "Israel"),
|
||||
("Jamaica", "Jamaica"),
|
||||
("Japan", "Japan"),
|
||||
("Kwajalein", "Kwajalein"),
|
||||
("Libya", "Libya"),
|
||||
("MET", "MET"),
|
||||
("MST", "MST"),
|
||||
("MST7MDT", "MST7MDT"),
|
||||
("NZ", "NZ"),
|
||||
("NZ-CHAT", "NZ-CHAT"),
|
||||
("Navajo", "Navajo"),
|
||||
("PRC", "PRC"),
|
||||
("PST8PDT", "PST8PDT"),
|
||||
("Poland", "Poland"),
|
||||
("Portugal", "Portugal"),
|
||||
("ROC", "ROC"),
|
||||
("ROK", "ROK"),
|
||||
("Singapore", "Singapore"),
|
||||
("Turkey", "Turkey"),
|
||||
("UCT", "UCT"),
|
||||
("UTC", "UTC"),
|
||||
("Universal", "Universal"),
|
||||
("W-SU", "W-SU"),
|
||||
("WET", "WET"),
|
||||
("Zulu", "Zulu"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Pacific",
|
||||
[
|
||||
("Pacific/Apia", "Apia"),
|
||||
("Pacific/Auckland", "Auckland"),
|
||||
("Pacific/Bougainville", "Bougainville"),
|
||||
("Pacific/Chatham", "Chatham"),
|
||||
("Pacific/Chuuk", "Chuuk"),
|
||||
("Pacific/Easter", "Easter"),
|
||||
("Pacific/Efate", "Efate"),
|
||||
("Pacific/Enderbury", "Enderbury"),
|
||||
("Pacific/Fakaofo", "Fakaofo"),
|
||||
("Pacific/Fiji", "Fiji"),
|
||||
("Pacific/Funafuti", "Funafuti"),
|
||||
("Pacific/Galapagos", "Galapagos"),
|
||||
("Pacific/Gambier", "Gambier"),
|
||||
("Pacific/Guadalcanal", "Guadalcanal"),
|
||||
("Pacific/Guam", "Guam"),
|
||||
("Pacific/Honolulu", "Honolulu"),
|
||||
("Pacific/Johnston", "Johnston"),
|
||||
("Pacific/Kanton", "Kanton"),
|
||||
("Pacific/Kiritimati", "Kiritimati"),
|
||||
("Pacific/Kosrae", "Kosrae"),
|
||||
("Pacific/Kwajalein", "Kwajalein"),
|
||||
("Pacific/Majuro", "Majuro"),
|
||||
("Pacific/Marquesas", "Marquesas"),
|
||||
("Pacific/Midway", "Midway"),
|
||||
("Pacific/Nauru", "Nauru"),
|
||||
("Pacific/Niue", "Niue"),
|
||||
("Pacific/Norfolk", "Norfolk"),
|
||||
("Pacific/Noumea", "Noumea"),
|
||||
("Pacific/Pago_Pago", "Pago_Pago"),
|
||||
("Pacific/Palau", "Palau"),
|
||||
("Pacific/Pitcairn", "Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pohnpei"),
|
||||
("Pacific/Ponape", "Ponape"),
|
||||
("Pacific/Port_Moresby", "Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Rarotonga"),
|
||||
("Pacific/Saipan", "Saipan"),
|
||||
("Pacific/Samoa", "Samoa"),
|
||||
("Pacific/Tahiti", "Tahiti"),
|
||||
("Pacific/Tarawa", "Tarawa"),
|
||||
("Pacific/Tongatapu", "Tongatapu"),
|
||||
("Pacific/Truk", "Truk"),
|
||||
("Pacific/Wake", "Wake"),
|
||||
("Pacific/Wallis", "Wallis"),
|
||||
("Pacific/Yap", "Yap"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"US",
|
||||
[
|
||||
("US/Alaska", "Alaska"),
|
||||
("US/Aleutian", "Aleutian"),
|
||||
("US/Arizona", "Arizona"),
|
||||
("US/Central", "Central"),
|
||||
("US/East-Indiana", "East-Indiana"),
|
||||
("US/Eastern", "Eastern"),
|
||||
("US/Hawaii", "Hawaii"),
|
||||
("US/Indiana-Starke", "Indiana-Starke"),
|
||||
("US/Michigan", "Michigan"),
|
||||
("US/Mountain", "Mountain"),
|
||||
("US/Pacific", "Pacific"),
|
||||
("US/Samoa", "Samoa"),
|
||||
],
|
||||
),
|
||||
],
|
||||
default="Asia/Ho_Chi_Minh",
|
||||
max_length=50,
|
||||
verbose_name="location",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OrganizationProfile",
|
||||
fields=[
|
||||
(
|
||||
"last_visit",
|
||||
models.AutoField(
|
||||
primary_key=True, serialize=False, verbose_name="last visit"
|
||||
),
|
||||
),
|
||||
(
|
||||
"organization",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="last_vist",
|
||||
to="judge.Organization",
|
||||
verbose_name="organization",
|
||||
),
|
||||
),
|
||||
(
|
||||
"users",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="last_visit",
|
||||
to="judge.Profile",
|
||||
verbose_name="user",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,676 +0,0 @@
|
|||
# Generated by Django 2.2.25 on 2022-10-27 20:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0134_auto_20221018_0124"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="profile",
|
||||
name="timezone",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
(
|
||||
"Africa",
|
||||
[
|
||||
("Africa/Abidjan", "Abidjan"),
|
||||
("Africa/Accra", "Accra"),
|
||||
("Africa/Addis_Ababa", "Addis_Ababa"),
|
||||
("Africa/Algiers", "Algiers"),
|
||||
("Africa/Asmara", "Asmara"),
|
||||
("Africa/Asmera", "Asmera"),
|
||||
("Africa/Bamako", "Bamako"),
|
||||
("Africa/Bangui", "Bangui"),
|
||||
("Africa/Banjul", "Banjul"),
|
||||
("Africa/Bissau", "Bissau"),
|
||||
("Africa/Blantyre", "Blantyre"),
|
||||
("Africa/Brazzaville", "Brazzaville"),
|
||||
("Africa/Bujumbura", "Bujumbura"),
|
||||
("Africa/Cairo", "Cairo"),
|
||||
("Africa/Casablanca", "Casablanca"),
|
||||
("Africa/Ceuta", "Ceuta"),
|
||||
("Africa/Conakry", "Conakry"),
|
||||
("Africa/Dakar", "Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Djibouti"),
|
||||
("Africa/Douala", "Douala"),
|
||||
("Africa/El_Aaiun", "El_Aaiun"),
|
||||
("Africa/Freetown", "Freetown"),
|
||||
("Africa/Gaborone", "Gaborone"),
|
||||
("Africa/Harare", "Harare"),
|
||||
("Africa/Johannesburg", "Johannesburg"),
|
||||
("Africa/Juba", "Juba"),
|
||||
("Africa/Kampala", "Kampala"),
|
||||
("Africa/Khartoum", "Khartoum"),
|
||||
("Africa/Kigali", "Kigali"),
|
||||
("Africa/Kinshasa", "Kinshasa"),
|
||||
("Africa/Lagos", "Lagos"),
|
||||
("Africa/Libreville", "Libreville"),
|
||||
("Africa/Lome", "Lome"),
|
||||
("Africa/Luanda", "Luanda"),
|
||||
("Africa/Lubumbashi", "Lubumbashi"),
|
||||
("Africa/Lusaka", "Lusaka"),
|
||||
("Africa/Malabo", "Malabo"),
|
||||
("Africa/Maputo", "Maputo"),
|
||||
("Africa/Maseru", "Maseru"),
|
||||
("Africa/Mbabane", "Mbabane"),
|
||||
("Africa/Mogadishu", "Mogadishu"),
|
||||
("Africa/Monrovia", "Monrovia"),
|
||||
("Africa/Nairobi", "Nairobi"),
|
||||
("Africa/Ndjamena", "Ndjamena"),
|
||||
("Africa/Niamey", "Niamey"),
|
||||
("Africa/Nouakchott", "Nouakchott"),
|
||||
("Africa/Ouagadougou", "Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Sao_Tome"),
|
||||
("Africa/Timbuktu", "Timbuktu"),
|
||||
("Africa/Tripoli", "Tripoli"),
|
||||
("Africa/Tunis", "Tunis"),
|
||||
("Africa/Windhoek", "Windhoek"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"America",
|
||||
[
|
||||
("America/Adak", "Adak"),
|
||||
("America/Anchorage", "Anchorage"),
|
||||
("America/Anguilla", "Anguilla"),
|
||||
("America/Antigua", "Antigua"),
|
||||
("America/Araguaina", "Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "Argentina/Catamarca"),
|
||||
(
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"Argentina/ComodRivadavia",
|
||||
),
|
||||
("America/Argentina/Cordoba", "Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "Argentina/Ushuaia"),
|
||||
("America/Aruba", "Aruba"),
|
||||
("America/Asuncion", "Asuncion"),
|
||||
("America/Atikokan", "Atikokan"),
|
||||
("America/Atka", "Atka"),
|
||||
("America/Bahia", "Bahia"),
|
||||
("America/Bahia_Banderas", "Bahia_Banderas"),
|
||||
("America/Barbados", "Barbados"),
|
||||
("America/Belem", "Belem"),
|
||||
("America/Belize", "Belize"),
|
||||
("America/Blanc-Sablon", "Blanc-Sablon"),
|
||||
("America/Boa_Vista", "Boa_Vista"),
|
||||
("America/Bogota", "Bogota"),
|
||||
("America/Boise", "Boise"),
|
||||
("America/Buenos_Aires", "Buenos_Aires"),
|
||||
("America/Cambridge_Bay", "Cambridge_Bay"),
|
||||
("America/Campo_Grande", "Campo_Grande"),
|
||||
("America/Cancun", "Cancun"),
|
||||
("America/Caracas", "Caracas"),
|
||||
("America/Catamarca", "Catamarca"),
|
||||
("America/Cayenne", "Cayenne"),
|
||||
("America/Cayman", "Cayman"),
|
||||
("America/Chicago", "Chicago"),
|
||||
("America/Chihuahua", "Chihuahua"),
|
||||
("America/Coral_Harbour", "Coral_Harbour"),
|
||||
("America/Cordoba", "Cordoba"),
|
||||
("America/Costa_Rica", "Costa_Rica"),
|
||||
("America/Creston", "Creston"),
|
||||
("America/Cuiaba", "Cuiaba"),
|
||||
("America/Curacao", "Curacao"),
|
||||
("America/Danmarkshavn", "Danmarkshavn"),
|
||||
("America/Dawson", "Dawson"),
|
||||
("America/Dawson_Creek", "Dawson_Creek"),
|
||||
("America/Denver", "Denver"),
|
||||
("America/Detroit", "Detroit"),
|
||||
("America/Dominica", "Dominica"),
|
||||
("America/Edmonton", "Edmonton"),
|
||||
("America/Eirunepe", "Eirunepe"),
|
||||
("America/El_Salvador", "El_Salvador"),
|
||||
("America/Ensenada", "Ensenada"),
|
||||
("America/Fort_Nelson", "Fort_Nelson"),
|
||||
("America/Fort_Wayne", "Fort_Wayne"),
|
||||
("America/Fortaleza", "Fortaleza"),
|
||||
("America/Glace_Bay", "Glace_Bay"),
|
||||
("America/Godthab", "Godthab"),
|
||||
("America/Goose_Bay", "Goose_Bay"),
|
||||
("America/Grand_Turk", "Grand_Turk"),
|
||||
("America/Grenada", "Grenada"),
|
||||
("America/Guadeloupe", "Guadeloupe"),
|
||||
("America/Guatemala", "Guatemala"),
|
||||
("America/Guayaquil", "Guayaquil"),
|
||||
("America/Guyana", "Guyana"),
|
||||
("America/Halifax", "Halifax"),
|
||||
("America/Havana", "Havana"),
|
||||
("America/Hermosillo", "Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "Indiana/Winamac"),
|
||||
("America/Indianapolis", "Indianapolis"),
|
||||
("America/Inuvik", "Inuvik"),
|
||||
("America/Iqaluit", "Iqaluit"),
|
||||
("America/Jamaica", "Jamaica"),
|
||||
("America/Jujuy", "Jujuy"),
|
||||
("America/Juneau", "Juneau"),
|
||||
("America/Kentucky/Louisville", "Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "Kentucky/Monticello"),
|
||||
("America/Knox_IN", "Knox_IN"),
|
||||
("America/Kralendijk", "Kralendijk"),
|
||||
("America/La_Paz", "La_Paz"),
|
||||
("America/Lima", "Lima"),
|
||||
("America/Los_Angeles", "Los_Angeles"),
|
||||
("America/Louisville", "Louisville"),
|
||||
("America/Lower_Princes", "Lower_Princes"),
|
||||
("America/Maceio", "Maceio"),
|
||||
("America/Managua", "Managua"),
|
||||
("America/Manaus", "Manaus"),
|
||||
("America/Marigot", "Marigot"),
|
||||
("America/Martinique", "Martinique"),
|
||||
("America/Matamoros", "Matamoros"),
|
||||
("America/Mazatlan", "Mazatlan"),
|
||||
("America/Mendoza", "Mendoza"),
|
||||
("America/Menominee", "Menominee"),
|
||||
("America/Merida", "Merida"),
|
||||
("America/Metlakatla", "Metlakatla"),
|
||||
("America/Mexico_City", "Mexico_City"),
|
||||
("America/Miquelon", "Miquelon"),
|
||||
("America/Moncton", "Moncton"),
|
||||
("America/Monterrey", "Monterrey"),
|
||||
("America/Montevideo", "Montevideo"),
|
||||
("America/Montreal", "Montreal"),
|
||||
("America/Montserrat", "Montserrat"),
|
||||
("America/Nassau", "Nassau"),
|
||||
("America/New_York", "New_York"),
|
||||
("America/Nipigon", "Nipigon"),
|
||||
("America/Nome", "Nome"),
|
||||
("America/Noronha", "Noronha"),
|
||||
("America/North_Dakota/Beulah", "North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "Nuuk"),
|
||||
("America/Ojinaga", "Ojinaga"),
|
||||
("America/Panama", "Panama"),
|
||||
("America/Pangnirtung", "Pangnirtung"),
|
||||
("America/Paramaribo", "Paramaribo"),
|
||||
("America/Phoenix", "Phoenix"),
|
||||
("America/Port-au-Prince", "Port-au-Prince"),
|
||||
("America/Port_of_Spain", "Port_of_Spain"),
|
||||
("America/Porto_Acre", "Porto_Acre"),
|
||||
("America/Porto_Velho", "Porto_Velho"),
|
||||
("America/Puerto_Rico", "Puerto_Rico"),
|
||||
("America/Punta_Arenas", "Punta_Arenas"),
|
||||
("America/Rainy_River", "Rainy_River"),
|
||||
("America/Rankin_Inlet", "Rankin_Inlet"),
|
||||
("America/Recife", "Recife"),
|
||||
("America/Regina", "Regina"),
|
||||
("America/Resolute", "Resolute"),
|
||||
("America/Rio_Branco", "Rio_Branco"),
|
||||
("America/Rosario", "Rosario"),
|
||||
("America/Santa_Isabel", "Santa_Isabel"),
|
||||
("America/Santarem", "Santarem"),
|
||||
("America/Santiago", "Santiago"),
|
||||
("America/Santo_Domingo", "Santo_Domingo"),
|
||||
("America/Sao_Paulo", "Sao_Paulo"),
|
||||
("America/Scoresbysund", "Scoresbysund"),
|
||||
("America/Shiprock", "Shiprock"),
|
||||
("America/Sitka", "Sitka"),
|
||||
("America/St_Barthelemy", "St_Barthelemy"),
|
||||
("America/St_Johns", "St_Johns"),
|
||||
("America/St_Kitts", "St_Kitts"),
|
||||
("America/St_Lucia", "St_Lucia"),
|
||||
("America/St_Thomas", "St_Thomas"),
|
||||
("America/St_Vincent", "St_Vincent"),
|
||||
("America/Swift_Current", "Swift_Current"),
|
||||
("America/Tegucigalpa", "Tegucigalpa"),
|
||||
("America/Thule", "Thule"),
|
||||
("America/Thunder_Bay", "Thunder_Bay"),
|
||||
("America/Tijuana", "Tijuana"),
|
||||
("America/Toronto", "Toronto"),
|
||||
("America/Tortola", "Tortola"),
|
||||
("America/Vancouver", "Vancouver"),
|
||||
("America/Virgin", "Virgin"),
|
||||
("America/Whitehorse", "Whitehorse"),
|
||||
("America/Winnipeg", "Winnipeg"),
|
||||
("America/Yakutat", "Yakutat"),
|
||||
("America/Yellowknife", "Yellowknife"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Antarctica",
|
||||
[
|
||||
("Antarctica/Casey", "Casey"),
|
||||
("Antarctica/Davis", "Davis"),
|
||||
("Antarctica/DumontDUrville", "DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Macquarie"),
|
||||
("Antarctica/Mawson", "Mawson"),
|
||||
("Antarctica/McMurdo", "McMurdo"),
|
||||
("Antarctica/Palmer", "Palmer"),
|
||||
("Antarctica/Rothera", "Rothera"),
|
||||
("Antarctica/South_Pole", "South_Pole"),
|
||||
("Antarctica/Syowa", "Syowa"),
|
||||
("Antarctica/Troll", "Troll"),
|
||||
("Antarctica/Vostok", "Vostok"),
|
||||
],
|
||||
),
|
||||
("Arctic", [("Arctic/Longyearbyen", "Longyearbyen")]),
|
||||
(
|
||||
"Asia",
|
||||
[
|
||||
("Asia/Aden", "Aden"),
|
||||
("Asia/Almaty", "Almaty"),
|
||||
("Asia/Amman", "Amman"),
|
||||
("Asia/Anadyr", "Anadyr"),
|
||||
("Asia/Aqtau", "Aqtau"),
|
||||
("Asia/Aqtobe", "Aqtobe"),
|
||||
("Asia/Ashgabat", "Ashgabat"),
|
||||
("Asia/Ashkhabad", "Ashkhabad"),
|
||||
("Asia/Atyrau", "Atyrau"),
|
||||
("Asia/Baghdad", "Baghdad"),
|
||||
("Asia/Bahrain", "Bahrain"),
|
||||
("Asia/Baku", "Baku"),
|
||||
("Asia/Bangkok", "Bangkok"),
|
||||
("Asia/Barnaul", "Barnaul"),
|
||||
("Asia/Beirut", "Beirut"),
|
||||
("Asia/Bishkek", "Bishkek"),
|
||||
("Asia/Brunei", "Brunei"),
|
||||
("Asia/Calcutta", "Calcutta"),
|
||||
("Asia/Chita", "Chita"),
|
||||
("Asia/Choibalsan", "Choibalsan"),
|
||||
("Asia/Chongqing", "Chongqing"),
|
||||
("Asia/Chungking", "Chungking"),
|
||||
("Asia/Colombo", "Colombo"),
|
||||
("Asia/Dacca", "Dacca"),
|
||||
("Asia/Damascus", "Damascus"),
|
||||
("Asia/Dhaka", "Dhaka"),
|
||||
("Asia/Dili", "Dili"),
|
||||
("Asia/Dubai", "Dubai"),
|
||||
("Asia/Dushanbe", "Dushanbe"),
|
||||
("Asia/Famagusta", "Famagusta"),
|
||||
("Asia/Gaza", "Gaza"),
|
||||
("Asia/Harbin", "Harbin"),
|
||||
("Asia/Hebron", "Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Hong_Kong"),
|
||||
("Asia/Hovd", "Hovd"),
|
||||
("Asia/Irkutsk", "Irkutsk"),
|
||||
("Asia/Istanbul", "Istanbul"),
|
||||
("Asia/Jakarta", "Jakarta"),
|
||||
("Asia/Jayapura", "Jayapura"),
|
||||
("Asia/Jerusalem", "Jerusalem"),
|
||||
("Asia/Kabul", "Kabul"),
|
||||
("Asia/Kamchatka", "Kamchatka"),
|
||||
("Asia/Karachi", "Karachi"),
|
||||
("Asia/Kashgar", "Kashgar"),
|
||||
("Asia/Kathmandu", "Kathmandu"),
|
||||
("Asia/Katmandu", "Katmandu"),
|
||||
("Asia/Khandyga", "Khandyga"),
|
||||
("Asia/Kolkata", "Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Kuching"),
|
||||
("Asia/Kuwait", "Kuwait"),
|
||||
("Asia/Macao", "Macao"),
|
||||
("Asia/Macau", "Macau"),
|
||||
("Asia/Magadan", "Magadan"),
|
||||
("Asia/Makassar", "Makassar"),
|
||||
("Asia/Manila", "Manila"),
|
||||
("Asia/Muscat", "Muscat"),
|
||||
("Asia/Nicosia", "Nicosia"),
|
||||
("Asia/Novokuznetsk", "Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Novosibirsk"),
|
||||
("Asia/Omsk", "Omsk"),
|
||||
("Asia/Oral", "Oral"),
|
||||
("Asia/Phnom_Penh", "Phnom_Penh"),
|
||||
("Asia/Pontianak", "Pontianak"),
|
||||
("Asia/Pyongyang", "Pyongyang"),
|
||||
("Asia/Qatar", "Qatar"),
|
||||
("Asia/Qostanay", "Qostanay"),
|
||||
("Asia/Qyzylorda", "Qyzylorda"),
|
||||
("Asia/Rangoon", "Rangoon"),
|
||||
("Asia/Riyadh", "Riyadh"),
|
||||
("Asia/Saigon", "Saigon"),
|
||||
("Asia/Sakhalin", "Sakhalin"),
|
||||
("Asia/Samarkand", "Samarkand"),
|
||||
("Asia/Seoul", "Seoul"),
|
||||
("Asia/Shanghai", "Shanghai"),
|
||||
("Asia/Singapore", "Singapore"),
|
||||
("Asia/Srednekolymsk", "Srednekolymsk"),
|
||||
("Asia/Taipei", "Taipei"),
|
||||
("Asia/Tashkent", "Tashkent"),
|
||||
("Asia/Tbilisi", "Tbilisi"),
|
||||
("Asia/Tehran", "Tehran"),
|
||||
("Asia/Tel_Aviv", "Tel_Aviv"),
|
||||
("Asia/Thimbu", "Thimbu"),
|
||||
("Asia/Thimphu", "Thimphu"),
|
||||
("Asia/Tokyo", "Tokyo"),
|
||||
("Asia/Tomsk", "Tomsk"),
|
||||
("Asia/Ujung_Pandang", "Ujung_Pandang"),
|
||||
("Asia/Ulaanbaatar", "Ulaanbaatar"),
|
||||
("Asia/Ulan_Bator", "Ulan_Bator"),
|
||||
("Asia/Urumqi", "Urumqi"),
|
||||
("Asia/Ust-Nera", "Ust-Nera"),
|
||||
("Asia/Vientiane", "Vientiane"),
|
||||
("Asia/Vladivostok", "Vladivostok"),
|
||||
("Asia/Yakutsk", "Yakutsk"),
|
||||
("Asia/Yangon", "Yangon"),
|
||||
("Asia/Yekaterinburg", "Yekaterinburg"),
|
||||
("Asia/Yerevan", "Yerevan"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Atlantic",
|
||||
[
|
||||
("Atlantic/Azores", "Azores"),
|
||||
("Atlantic/Bermuda", "Bermuda"),
|
||||
("Atlantic/Canary", "Canary"),
|
||||
("Atlantic/Cape_Verde", "Cape_Verde"),
|
||||
("Atlantic/Faeroe", "Faeroe"),
|
||||
("Atlantic/Faroe", "Faroe"),
|
||||
("Atlantic/Jan_Mayen", "Jan_Mayen"),
|
||||
("Atlantic/Madeira", "Madeira"),
|
||||
("Atlantic/Reykjavik", "Reykjavik"),
|
||||
("Atlantic/South_Georgia", "South_Georgia"),
|
||||
("Atlantic/St_Helena", "St_Helena"),
|
||||
("Atlantic/Stanley", "Stanley"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Australia",
|
||||
[
|
||||
("Australia/ACT", "ACT"),
|
||||
("Australia/Adelaide", "Adelaide"),
|
||||
("Australia/Brisbane", "Brisbane"),
|
||||
("Australia/Broken_Hill", "Broken_Hill"),
|
||||
("Australia/Canberra", "Canberra"),
|
||||
("Australia/Currie", "Currie"),
|
||||
("Australia/Darwin", "Darwin"),
|
||||
("Australia/Eucla", "Eucla"),
|
||||
("Australia/Hobart", "Hobart"),
|
||||
("Australia/LHI", "LHI"),
|
||||
("Australia/Lindeman", "Lindeman"),
|
||||
("Australia/Lord_Howe", "Lord_Howe"),
|
||||
("Australia/Melbourne", "Melbourne"),
|
||||
("Australia/NSW", "NSW"),
|
||||
("Australia/North", "North"),
|
||||
("Australia/Perth", "Perth"),
|
||||
("Australia/Queensland", "Queensland"),
|
||||
("Australia/South", "South"),
|
||||
("Australia/Sydney", "Sydney"),
|
||||
("Australia/Tasmania", "Tasmania"),
|
||||
("Australia/Victoria", "Victoria"),
|
||||
("Australia/West", "West"),
|
||||
("Australia/Yancowinna", "Yancowinna"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Brazil",
|
||||
[
|
||||
("Brazil/Acre", "Acre"),
|
||||
("Brazil/DeNoronha", "DeNoronha"),
|
||||
("Brazil/East", "East"),
|
||||
("Brazil/West", "West"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Canada",
|
||||
[
|
||||
("Canada/Atlantic", "Atlantic"),
|
||||
("Canada/Central", "Central"),
|
||||
("Canada/Eastern", "Eastern"),
|
||||
("Canada/Mountain", "Mountain"),
|
||||
("Canada/Newfoundland", "Newfoundland"),
|
||||
("Canada/Pacific", "Pacific"),
|
||||
("Canada/Saskatchewan", "Saskatchewan"),
|
||||
("Canada/Yukon", "Yukon"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Chile",
|
||||
[
|
||||
("Chile/Continental", "Continental"),
|
||||
("Chile/EasterIsland", "EasterIsland"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Etc",
|
||||
[
|
||||
("Etc/Greenwich", "Greenwich"),
|
||||
("Etc/UCT", "UCT"),
|
||||
("Etc/UTC", "UTC"),
|
||||
("Etc/Universal", "Universal"),
|
||||
("Etc/Zulu", "Zulu"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Europe",
|
||||
[
|
||||
("Europe/Amsterdam", "Amsterdam"),
|
||||
("Europe/Andorra", "Andorra"),
|
||||
("Europe/Astrakhan", "Astrakhan"),
|
||||
("Europe/Athens", "Athens"),
|
||||
("Europe/Belfast", "Belfast"),
|
||||
("Europe/Belgrade", "Belgrade"),
|
||||
("Europe/Berlin", "Berlin"),
|
||||
("Europe/Bratislava", "Bratislava"),
|
||||
("Europe/Brussels", "Brussels"),
|
||||
("Europe/Bucharest", "Bucharest"),
|
||||
("Europe/Budapest", "Budapest"),
|
||||
("Europe/Busingen", "Busingen"),
|
||||
("Europe/Chisinau", "Chisinau"),
|
||||
("Europe/Copenhagen", "Copenhagen"),
|
||||
("Europe/Dublin", "Dublin"),
|
||||
("Europe/Gibraltar", "Gibraltar"),
|
||||
("Europe/Guernsey", "Guernsey"),
|
||||
("Europe/Helsinki", "Helsinki"),
|
||||
("Europe/Isle_of_Man", "Isle_of_Man"),
|
||||
("Europe/Istanbul", "Istanbul"),
|
||||
("Europe/Jersey", "Jersey"),
|
||||
("Europe/Kaliningrad", "Kaliningrad"),
|
||||
("Europe/Kiev", "Kiev"),
|
||||
("Europe/Kirov", "Kirov"),
|
||||
("Europe/Lisbon", "Lisbon"),
|
||||
("Europe/Ljubljana", "Ljubljana"),
|
||||
("Europe/London", "London"),
|
||||
("Europe/Luxembourg", "Luxembourg"),
|
||||
("Europe/Madrid", "Madrid"),
|
||||
("Europe/Malta", "Malta"),
|
||||
("Europe/Mariehamn", "Mariehamn"),
|
||||
("Europe/Minsk", "Minsk"),
|
||||
("Europe/Monaco", "Monaco"),
|
||||
("Europe/Moscow", "Moscow"),
|
||||
("Europe/Nicosia", "Nicosia"),
|
||||
("Europe/Oslo", "Oslo"),
|
||||
("Europe/Paris", "Paris"),
|
||||
("Europe/Podgorica", "Podgorica"),
|
||||
("Europe/Prague", "Prague"),
|
||||
("Europe/Riga", "Riga"),
|
||||
("Europe/Rome", "Rome"),
|
||||
("Europe/Samara", "Samara"),
|
||||
("Europe/San_Marino", "San_Marino"),
|
||||
("Europe/Sarajevo", "Sarajevo"),
|
||||
("Europe/Saratov", "Saratov"),
|
||||
("Europe/Simferopol", "Simferopol"),
|
||||
("Europe/Skopje", "Skopje"),
|
||||
("Europe/Sofia", "Sofia"),
|
||||
("Europe/Stockholm", "Stockholm"),
|
||||
("Europe/Tallinn", "Tallinn"),
|
||||
("Europe/Tirane", "Tirane"),
|
||||
("Europe/Tiraspol", "Tiraspol"),
|
||||
("Europe/Ulyanovsk", "Ulyanovsk"),
|
||||
("Europe/Uzhgorod", "Uzhgorod"),
|
||||
("Europe/Vaduz", "Vaduz"),
|
||||
("Europe/Vatican", "Vatican"),
|
||||
("Europe/Vienna", "Vienna"),
|
||||
("Europe/Vilnius", "Vilnius"),
|
||||
("Europe/Volgograd", "Volgograd"),
|
||||
("Europe/Warsaw", "Warsaw"),
|
||||
("Europe/Zagreb", "Zagreb"),
|
||||
("Europe/Zaporozhye", "Zaporozhye"),
|
||||
("Europe/Zurich", "Zurich"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Indian",
|
||||
[
|
||||
("Indian/Antananarivo", "Antananarivo"),
|
||||
("Indian/Chagos", "Chagos"),
|
||||
("Indian/Christmas", "Christmas"),
|
||||
("Indian/Cocos", "Cocos"),
|
||||
("Indian/Comoro", "Comoro"),
|
||||
("Indian/Kerguelen", "Kerguelen"),
|
||||
("Indian/Mahe", "Mahe"),
|
||||
("Indian/Maldives", "Maldives"),
|
||||
("Indian/Mauritius", "Mauritius"),
|
||||
("Indian/Mayotte", "Mayotte"),
|
||||
("Indian/Reunion", "Reunion"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Mexico",
|
||||
[
|
||||
("Mexico/BajaNorte", "BajaNorte"),
|
||||
("Mexico/BajaSur", "BajaSur"),
|
||||
("Mexico/General", "General"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Other",
|
||||
[
|
||||
("CET", "CET"),
|
||||
("CST6CDT", "CST6CDT"),
|
||||
("Cuba", "Cuba"),
|
||||
("EET", "EET"),
|
||||
("EST", "EST"),
|
||||
("EST5EDT", "EST5EDT"),
|
||||
("Egypt", "Egypt"),
|
||||
("Eire", "Eire"),
|
||||
("GB", "GB"),
|
||||
("GB-Eire", "GB-Eire"),
|
||||
("Greenwich", "Greenwich"),
|
||||
("HST", "HST"),
|
||||
("Hongkong", "Hongkong"),
|
||||
("Iceland", "Iceland"),
|
||||
("Iran", "Iran"),
|
||||
("Israel", "Israel"),
|
||||
("Jamaica", "Jamaica"),
|
||||
("Japan", "Japan"),
|
||||
("Kwajalein", "Kwajalein"),
|
||||
("Libya", "Libya"),
|
||||
("MET", "MET"),
|
||||
("MST", "MST"),
|
||||
("MST7MDT", "MST7MDT"),
|
||||
("NZ", "NZ"),
|
||||
("NZ-CHAT", "NZ-CHAT"),
|
||||
("Navajo", "Navajo"),
|
||||
("PRC", "PRC"),
|
||||
("PST8PDT", "PST8PDT"),
|
||||
("Poland", "Poland"),
|
||||
("Portugal", "Portugal"),
|
||||
("ROC", "ROC"),
|
||||
("ROK", "ROK"),
|
||||
("Singapore", "Singapore"),
|
||||
("Turkey", "Turkey"),
|
||||
("UCT", "UCT"),
|
||||
("UTC", "UTC"),
|
||||
("Universal", "Universal"),
|
||||
("W-SU", "W-SU"),
|
||||
("WET", "WET"),
|
||||
("Zulu", "Zulu"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Pacific",
|
||||
[
|
||||
("Pacific/Apia", "Apia"),
|
||||
("Pacific/Auckland", "Auckland"),
|
||||
("Pacific/Bougainville", "Bougainville"),
|
||||
("Pacific/Chatham", "Chatham"),
|
||||
("Pacific/Chuuk", "Chuuk"),
|
||||
("Pacific/Easter", "Easter"),
|
||||
("Pacific/Efate", "Efate"),
|
||||
("Pacific/Enderbury", "Enderbury"),
|
||||
("Pacific/Fakaofo", "Fakaofo"),
|
||||
("Pacific/Fiji", "Fiji"),
|
||||
("Pacific/Funafuti", "Funafuti"),
|
||||
("Pacific/Galapagos", "Galapagos"),
|
||||
("Pacific/Gambier", "Gambier"),
|
||||
("Pacific/Guadalcanal", "Guadalcanal"),
|
||||
("Pacific/Guam", "Guam"),
|
||||
("Pacific/Honolulu", "Honolulu"),
|
||||
("Pacific/Johnston", "Johnston"),
|
||||
("Pacific/Kanton", "Kanton"),
|
||||
("Pacific/Kiritimati", "Kiritimati"),
|
||||
("Pacific/Kosrae", "Kosrae"),
|
||||
("Pacific/Kwajalein", "Kwajalein"),
|
||||
("Pacific/Majuro", "Majuro"),
|
||||
("Pacific/Marquesas", "Marquesas"),
|
||||
("Pacific/Midway", "Midway"),
|
||||
("Pacific/Nauru", "Nauru"),
|
||||
("Pacific/Niue", "Niue"),
|
||||
("Pacific/Norfolk", "Norfolk"),
|
||||
("Pacific/Noumea", "Noumea"),
|
||||
("Pacific/Pago_Pago", "Pago_Pago"),
|
||||
("Pacific/Palau", "Palau"),
|
||||
("Pacific/Pitcairn", "Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pohnpei"),
|
||||
("Pacific/Ponape", "Ponape"),
|
||||
("Pacific/Port_Moresby", "Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Rarotonga"),
|
||||
("Pacific/Saipan", "Saipan"),
|
||||
("Pacific/Samoa", "Samoa"),
|
||||
("Pacific/Tahiti", "Tahiti"),
|
||||
("Pacific/Tarawa", "Tarawa"),
|
||||
("Pacific/Tongatapu", "Tongatapu"),
|
||||
("Pacific/Truk", "Truk"),
|
||||
("Pacific/Wake", "Wake"),
|
||||
("Pacific/Wallis", "Wallis"),
|
||||
("Pacific/Yap", "Yap"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"US",
|
||||
[
|
||||
("US/Alaska", "Alaska"),
|
||||
("US/Aleutian", "Aleutian"),
|
||||
("US/Arizona", "Arizona"),
|
||||
("US/Central", "Central"),
|
||||
("US/East-Indiana", "East-Indiana"),
|
||||
("US/Eastern", "Eastern"),
|
||||
("US/Hawaii", "Hawaii"),
|
||||
("US/Indiana-Starke", "Indiana-Starke"),
|
||||
("US/Michigan", "Michigan"),
|
||||
("US/Mountain", "Mountain"),
|
||||
("US/Pacific", "Pacific"),
|
||||
("US/Samoa", "Samoa"),
|
||||
],
|
||||
),
|
||||
],
|
||||
default="Asia/Ho_Chi_Minh",
|
||||
max_length=50,
|
||||
verbose_name="location",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,677 +0,0 @@
|
|||
# Generated by Django 3.2.16 on 2022-11-01 01:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0135_auto_20221028_0300"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="profile",
|
||||
name="timezone",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
(
|
||||
"Africa",
|
||||
[
|
||||
("Africa/Abidjan", "Abidjan"),
|
||||
("Africa/Accra", "Accra"),
|
||||
("Africa/Addis_Ababa", "Addis_Ababa"),
|
||||
("Africa/Algiers", "Algiers"),
|
||||
("Africa/Asmara", "Asmara"),
|
||||
("Africa/Asmera", "Asmera"),
|
||||
("Africa/Bamako", "Bamako"),
|
||||
("Africa/Bangui", "Bangui"),
|
||||
("Africa/Banjul", "Banjul"),
|
||||
("Africa/Bissau", "Bissau"),
|
||||
("Africa/Blantyre", "Blantyre"),
|
||||
("Africa/Brazzaville", "Brazzaville"),
|
||||
("Africa/Bujumbura", "Bujumbura"),
|
||||
("Africa/Cairo", "Cairo"),
|
||||
("Africa/Casablanca", "Casablanca"),
|
||||
("Africa/Ceuta", "Ceuta"),
|
||||
("Africa/Conakry", "Conakry"),
|
||||
("Africa/Dakar", "Dakar"),
|
||||
("Africa/Dar_es_Salaam", "Dar_es_Salaam"),
|
||||
("Africa/Djibouti", "Djibouti"),
|
||||
("Africa/Douala", "Douala"),
|
||||
("Africa/El_Aaiun", "El_Aaiun"),
|
||||
("Africa/Freetown", "Freetown"),
|
||||
("Africa/Gaborone", "Gaborone"),
|
||||
("Africa/Harare", "Harare"),
|
||||
("Africa/Johannesburg", "Johannesburg"),
|
||||
("Africa/Juba", "Juba"),
|
||||
("Africa/Kampala", "Kampala"),
|
||||
("Africa/Khartoum", "Khartoum"),
|
||||
("Africa/Kigali", "Kigali"),
|
||||
("Africa/Kinshasa", "Kinshasa"),
|
||||
("Africa/Lagos", "Lagos"),
|
||||
("Africa/Libreville", "Libreville"),
|
||||
("Africa/Lome", "Lome"),
|
||||
("Africa/Luanda", "Luanda"),
|
||||
("Africa/Lubumbashi", "Lubumbashi"),
|
||||
("Africa/Lusaka", "Lusaka"),
|
||||
("Africa/Malabo", "Malabo"),
|
||||
("Africa/Maputo", "Maputo"),
|
||||
("Africa/Maseru", "Maseru"),
|
||||
("Africa/Mbabane", "Mbabane"),
|
||||
("Africa/Mogadishu", "Mogadishu"),
|
||||
("Africa/Monrovia", "Monrovia"),
|
||||
("Africa/Nairobi", "Nairobi"),
|
||||
("Africa/Ndjamena", "Ndjamena"),
|
||||
("Africa/Niamey", "Niamey"),
|
||||
("Africa/Nouakchott", "Nouakchott"),
|
||||
("Africa/Ouagadougou", "Ouagadougou"),
|
||||
("Africa/Porto-Novo", "Porto-Novo"),
|
||||
("Africa/Sao_Tome", "Sao_Tome"),
|
||||
("Africa/Timbuktu", "Timbuktu"),
|
||||
("Africa/Tripoli", "Tripoli"),
|
||||
("Africa/Tunis", "Tunis"),
|
||||
("Africa/Windhoek", "Windhoek"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"America",
|
||||
[
|
||||
("America/Adak", "Adak"),
|
||||
("America/Anchorage", "Anchorage"),
|
||||
("America/Anguilla", "Anguilla"),
|
||||
("America/Antigua", "Antigua"),
|
||||
("America/Araguaina", "Araguaina"),
|
||||
(
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"Argentina/Buenos_Aires",
|
||||
),
|
||||
("America/Argentina/Catamarca", "Argentina/Catamarca"),
|
||||
(
|
||||
"America/Argentina/ComodRivadavia",
|
||||
"Argentina/ComodRivadavia",
|
||||
),
|
||||
("America/Argentina/Cordoba", "Argentina/Cordoba"),
|
||||
("America/Argentina/Jujuy", "Argentina/Jujuy"),
|
||||
("America/Argentina/La_Rioja", "Argentina/La_Rioja"),
|
||||
("America/Argentina/Mendoza", "Argentina/Mendoza"),
|
||||
(
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"Argentina/Rio_Gallegos",
|
||||
),
|
||||
("America/Argentina/Salta", "Argentina/Salta"),
|
||||
("America/Argentina/San_Juan", "Argentina/San_Juan"),
|
||||
("America/Argentina/San_Luis", "Argentina/San_Luis"),
|
||||
("America/Argentina/Tucuman", "Argentina/Tucuman"),
|
||||
("America/Argentina/Ushuaia", "Argentina/Ushuaia"),
|
||||
("America/Aruba", "Aruba"),
|
||||
("America/Asuncion", "Asuncion"),
|
||||
("America/Atikokan", "Atikokan"),
|
||||
("America/Atka", "Atka"),
|
||||
("America/Bahia", "Bahia"),
|
||||
("America/Bahia_Banderas", "Bahia_Banderas"),
|
||||
("America/Barbados", "Barbados"),
|
||||
("America/Belem", "Belem"),
|
||||
("America/Belize", "Belize"),
|
||||
("America/Blanc-Sablon", "Blanc-Sablon"),
|
||||
("America/Boa_Vista", "Boa_Vista"),
|
||||
("America/Bogota", "Bogota"),
|
||||
("America/Boise", "Boise"),
|
||||
("America/Buenos_Aires", "Buenos_Aires"),
|
||||
("America/Cambridge_Bay", "Cambridge_Bay"),
|
||||
("America/Campo_Grande", "Campo_Grande"),
|
||||
("America/Cancun", "Cancun"),
|
||||
("America/Caracas", "Caracas"),
|
||||
("America/Catamarca", "Catamarca"),
|
||||
("America/Cayenne", "Cayenne"),
|
||||
("America/Cayman", "Cayman"),
|
||||
("America/Chicago", "Chicago"),
|
||||
("America/Chihuahua", "Chihuahua"),
|
||||
("America/Coral_Harbour", "Coral_Harbour"),
|
||||
("America/Cordoba", "Cordoba"),
|
||||
("America/Costa_Rica", "Costa_Rica"),
|
||||
("America/Creston", "Creston"),
|
||||
("America/Cuiaba", "Cuiaba"),
|
||||
("America/Curacao", "Curacao"),
|
||||
("America/Danmarkshavn", "Danmarkshavn"),
|
||||
("America/Dawson", "Dawson"),
|
||||
("America/Dawson_Creek", "Dawson_Creek"),
|
||||
("America/Denver", "Denver"),
|
||||
("America/Detroit", "Detroit"),
|
||||
("America/Dominica", "Dominica"),
|
||||
("America/Edmonton", "Edmonton"),
|
||||
("America/Eirunepe", "Eirunepe"),
|
||||
("America/El_Salvador", "El_Salvador"),
|
||||
("America/Ensenada", "Ensenada"),
|
||||
("America/Fort_Nelson", "Fort_Nelson"),
|
||||
("America/Fort_Wayne", "Fort_Wayne"),
|
||||
("America/Fortaleza", "Fortaleza"),
|
||||
("America/Glace_Bay", "Glace_Bay"),
|
||||
("America/Godthab", "Godthab"),
|
||||
("America/Goose_Bay", "Goose_Bay"),
|
||||
("America/Grand_Turk", "Grand_Turk"),
|
||||
("America/Grenada", "Grenada"),
|
||||
("America/Guadeloupe", "Guadeloupe"),
|
||||
("America/Guatemala", "Guatemala"),
|
||||
("America/Guayaquil", "Guayaquil"),
|
||||
("America/Guyana", "Guyana"),
|
||||
("America/Halifax", "Halifax"),
|
||||
("America/Havana", "Havana"),
|
||||
("America/Hermosillo", "Hermosillo"),
|
||||
("America/Indiana/Indianapolis", "Indiana/Indianapolis"),
|
||||
("America/Indiana/Knox", "Indiana/Knox"),
|
||||
("America/Indiana/Marengo", "Indiana/Marengo"),
|
||||
("America/Indiana/Petersburg", "Indiana/Petersburg"),
|
||||
("America/Indiana/Tell_City", "Indiana/Tell_City"),
|
||||
("America/Indiana/Vevay", "Indiana/Vevay"),
|
||||
("America/Indiana/Vincennes", "Indiana/Vincennes"),
|
||||
("America/Indiana/Winamac", "Indiana/Winamac"),
|
||||
("America/Indianapolis", "Indianapolis"),
|
||||
("America/Inuvik", "Inuvik"),
|
||||
("America/Iqaluit", "Iqaluit"),
|
||||
("America/Jamaica", "Jamaica"),
|
||||
("America/Jujuy", "Jujuy"),
|
||||
("America/Juneau", "Juneau"),
|
||||
("America/Kentucky/Louisville", "Kentucky/Louisville"),
|
||||
("America/Kentucky/Monticello", "Kentucky/Monticello"),
|
||||
("America/Knox_IN", "Knox_IN"),
|
||||
("America/Kralendijk", "Kralendijk"),
|
||||
("America/La_Paz", "La_Paz"),
|
||||
("America/Lima", "Lima"),
|
||||
("America/Los_Angeles", "Los_Angeles"),
|
||||
("America/Louisville", "Louisville"),
|
||||
("America/Lower_Princes", "Lower_Princes"),
|
||||
("America/Maceio", "Maceio"),
|
||||
("America/Managua", "Managua"),
|
||||
("America/Manaus", "Manaus"),
|
||||
("America/Marigot", "Marigot"),
|
||||
("America/Martinique", "Martinique"),
|
||||
("America/Matamoros", "Matamoros"),
|
||||
("America/Mazatlan", "Mazatlan"),
|
||||
("America/Mendoza", "Mendoza"),
|
||||
("America/Menominee", "Menominee"),
|
||||
("America/Merida", "Merida"),
|
||||
("America/Metlakatla", "Metlakatla"),
|
||||
("America/Mexico_City", "Mexico_City"),
|
||||
("America/Miquelon", "Miquelon"),
|
||||
("America/Moncton", "Moncton"),
|
||||
("America/Monterrey", "Monterrey"),
|
||||
("America/Montevideo", "Montevideo"),
|
||||
("America/Montreal", "Montreal"),
|
||||
("America/Montserrat", "Montserrat"),
|
||||
("America/Nassau", "Nassau"),
|
||||
("America/New_York", "New_York"),
|
||||
("America/Nipigon", "Nipigon"),
|
||||
("America/Nome", "Nome"),
|
||||
("America/Noronha", "Noronha"),
|
||||
("America/North_Dakota/Beulah", "North_Dakota/Beulah"),
|
||||
("America/North_Dakota/Center", "North_Dakota/Center"),
|
||||
(
|
||||
"America/North_Dakota/New_Salem",
|
||||
"North_Dakota/New_Salem",
|
||||
),
|
||||
("America/Nuuk", "Nuuk"),
|
||||
("America/Ojinaga", "Ojinaga"),
|
||||
("America/Panama", "Panama"),
|
||||
("America/Pangnirtung", "Pangnirtung"),
|
||||
("America/Paramaribo", "Paramaribo"),
|
||||
("America/Phoenix", "Phoenix"),
|
||||
("America/Port-au-Prince", "Port-au-Prince"),
|
||||
("America/Port_of_Spain", "Port_of_Spain"),
|
||||
("America/Porto_Acre", "Porto_Acre"),
|
||||
("America/Porto_Velho", "Porto_Velho"),
|
||||
("America/Puerto_Rico", "Puerto_Rico"),
|
||||
("America/Punta_Arenas", "Punta_Arenas"),
|
||||
("America/Rainy_River", "Rainy_River"),
|
||||
("America/Rankin_Inlet", "Rankin_Inlet"),
|
||||
("America/Recife", "Recife"),
|
||||
("America/Regina", "Regina"),
|
||||
("America/Resolute", "Resolute"),
|
||||
("America/Rio_Branco", "Rio_Branco"),
|
||||
("America/Rosario", "Rosario"),
|
||||
("America/Santa_Isabel", "Santa_Isabel"),
|
||||
("America/Santarem", "Santarem"),
|
||||
("America/Santiago", "Santiago"),
|
||||
("America/Santo_Domingo", "Santo_Domingo"),
|
||||
("America/Sao_Paulo", "Sao_Paulo"),
|
||||
("America/Scoresbysund", "Scoresbysund"),
|
||||
("America/Shiprock", "Shiprock"),
|
||||
("America/Sitka", "Sitka"),
|
||||
("America/St_Barthelemy", "St_Barthelemy"),
|
||||
("America/St_Johns", "St_Johns"),
|
||||
("America/St_Kitts", "St_Kitts"),
|
||||
("America/St_Lucia", "St_Lucia"),
|
||||
("America/St_Thomas", "St_Thomas"),
|
||||
("America/St_Vincent", "St_Vincent"),
|
||||
("America/Swift_Current", "Swift_Current"),
|
||||
("America/Tegucigalpa", "Tegucigalpa"),
|
||||
("America/Thule", "Thule"),
|
||||
("America/Thunder_Bay", "Thunder_Bay"),
|
||||
("America/Tijuana", "Tijuana"),
|
||||
("America/Toronto", "Toronto"),
|
||||
("America/Tortola", "Tortola"),
|
||||
("America/Vancouver", "Vancouver"),
|
||||
("America/Virgin", "Virgin"),
|
||||
("America/Whitehorse", "Whitehorse"),
|
||||
("America/Winnipeg", "Winnipeg"),
|
||||
("America/Yakutat", "Yakutat"),
|
||||
("America/Yellowknife", "Yellowknife"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Antarctica",
|
||||
[
|
||||
("Antarctica/Casey", "Casey"),
|
||||
("Antarctica/Davis", "Davis"),
|
||||
("Antarctica/DumontDUrville", "DumontDUrville"),
|
||||
("Antarctica/Macquarie", "Macquarie"),
|
||||
("Antarctica/Mawson", "Mawson"),
|
||||
("Antarctica/McMurdo", "McMurdo"),
|
||||
("Antarctica/Palmer", "Palmer"),
|
||||
("Antarctica/Rothera", "Rothera"),
|
||||
("Antarctica/South_Pole", "South_Pole"),
|
||||
("Antarctica/Syowa", "Syowa"),
|
||||
("Antarctica/Troll", "Troll"),
|
||||
("Antarctica/Vostok", "Vostok"),
|
||||
],
|
||||
),
|
||||
("Arctic", [("Arctic/Longyearbyen", "Longyearbyen")]),
|
||||
(
|
||||
"Asia",
|
||||
[
|
||||
("Asia/Aden", "Aden"),
|
||||
("Asia/Almaty", "Almaty"),
|
||||
("Asia/Amman", "Amman"),
|
||||
("Asia/Anadyr", "Anadyr"),
|
||||
("Asia/Aqtau", "Aqtau"),
|
||||
("Asia/Aqtobe", "Aqtobe"),
|
||||
("Asia/Ashgabat", "Ashgabat"),
|
||||
("Asia/Ashkhabad", "Ashkhabad"),
|
||||
("Asia/Atyrau", "Atyrau"),
|
||||
("Asia/Baghdad", "Baghdad"),
|
||||
("Asia/Bahrain", "Bahrain"),
|
||||
("Asia/Baku", "Baku"),
|
||||
("Asia/Bangkok", "Bangkok"),
|
||||
("Asia/Barnaul", "Barnaul"),
|
||||
("Asia/Beirut", "Beirut"),
|
||||
("Asia/Bishkek", "Bishkek"),
|
||||
("Asia/Brunei", "Brunei"),
|
||||
("Asia/Calcutta", "Calcutta"),
|
||||
("Asia/Chita", "Chita"),
|
||||
("Asia/Choibalsan", "Choibalsan"),
|
||||
("Asia/Chongqing", "Chongqing"),
|
||||
("Asia/Chungking", "Chungking"),
|
||||
("Asia/Colombo", "Colombo"),
|
||||
("Asia/Dacca", "Dacca"),
|
||||
("Asia/Damascus", "Damascus"),
|
||||
("Asia/Dhaka", "Dhaka"),
|
||||
("Asia/Dili", "Dili"),
|
||||
("Asia/Dubai", "Dubai"),
|
||||
("Asia/Dushanbe", "Dushanbe"),
|
||||
("Asia/Famagusta", "Famagusta"),
|
||||
("Asia/Gaza", "Gaza"),
|
||||
("Asia/Harbin", "Harbin"),
|
||||
("Asia/Hebron", "Hebron"),
|
||||
("Asia/Ho_Chi_Minh", "Ho_Chi_Minh"),
|
||||
("Asia/Hong_Kong", "Hong_Kong"),
|
||||
("Asia/Hovd", "Hovd"),
|
||||
("Asia/Irkutsk", "Irkutsk"),
|
||||
("Asia/Istanbul", "Istanbul"),
|
||||
("Asia/Jakarta", "Jakarta"),
|
||||
("Asia/Jayapura", "Jayapura"),
|
||||
("Asia/Jerusalem", "Jerusalem"),
|
||||
("Asia/Kabul", "Kabul"),
|
||||
("Asia/Kamchatka", "Kamchatka"),
|
||||
("Asia/Karachi", "Karachi"),
|
||||
("Asia/Kashgar", "Kashgar"),
|
||||
("Asia/Kathmandu", "Kathmandu"),
|
||||
("Asia/Katmandu", "Katmandu"),
|
||||
("Asia/Khandyga", "Khandyga"),
|
||||
("Asia/Kolkata", "Kolkata"),
|
||||
("Asia/Krasnoyarsk", "Krasnoyarsk"),
|
||||
("Asia/Kuala_Lumpur", "Kuala_Lumpur"),
|
||||
("Asia/Kuching", "Kuching"),
|
||||
("Asia/Kuwait", "Kuwait"),
|
||||
("Asia/Macao", "Macao"),
|
||||
("Asia/Macau", "Macau"),
|
||||
("Asia/Magadan", "Magadan"),
|
||||
("Asia/Makassar", "Makassar"),
|
||||
("Asia/Manila", "Manila"),
|
||||
("Asia/Muscat", "Muscat"),
|
||||
("Asia/Nicosia", "Nicosia"),
|
||||
("Asia/Novokuznetsk", "Novokuznetsk"),
|
||||
("Asia/Novosibirsk", "Novosibirsk"),
|
||||
("Asia/Omsk", "Omsk"),
|
||||
("Asia/Oral", "Oral"),
|
||||
("Asia/Phnom_Penh", "Phnom_Penh"),
|
||||
("Asia/Pontianak", "Pontianak"),
|
||||
("Asia/Pyongyang", "Pyongyang"),
|
||||
("Asia/Qatar", "Qatar"),
|
||||
("Asia/Qostanay", "Qostanay"),
|
||||
("Asia/Qyzylorda", "Qyzylorda"),
|
||||
("Asia/Rangoon", "Rangoon"),
|
||||
("Asia/Riyadh", "Riyadh"),
|
||||
("Asia/Saigon", "Saigon"),
|
||||
("Asia/Sakhalin", "Sakhalin"),
|
||||
("Asia/Samarkand", "Samarkand"),
|
||||
("Asia/Seoul", "Seoul"),
|
||||
("Asia/Shanghai", "Shanghai"),
|
||||
("Asia/Singapore", "Singapore"),
|
||||
("Asia/Srednekolymsk", "Srednekolymsk"),
|
||||
("Asia/Taipei", "Taipei"),
|
||||
("Asia/Tashkent", "Tashkent"),
|
||||
("Asia/Tbilisi", "Tbilisi"),
|
||||
("Asia/Tehran", "Tehran"),
|
||||
("Asia/Tel_Aviv", "Tel_Aviv"),
|
||||
("Asia/Thimbu", "Thimbu"),
|
||||
("Asia/Thimphu", "Thimphu"),
|
||||
("Asia/Tokyo", "Tokyo"),
|
||||
("Asia/Tomsk", "Tomsk"),
|
||||
("Asia/Ujung_Pandang", "Ujung_Pandang"),
|
||||
("Asia/Ulaanbaatar", "Ulaanbaatar"),
|
||||
("Asia/Ulan_Bator", "Ulan_Bator"),
|
||||
("Asia/Urumqi", "Urumqi"),
|
||||
("Asia/Ust-Nera", "Ust-Nera"),
|
||||
("Asia/Vientiane", "Vientiane"),
|
||||
("Asia/Vladivostok", "Vladivostok"),
|
||||
("Asia/Yakutsk", "Yakutsk"),
|
||||
("Asia/Yangon", "Yangon"),
|
||||
("Asia/Yekaterinburg", "Yekaterinburg"),
|
||||
("Asia/Yerevan", "Yerevan"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Atlantic",
|
||||
[
|
||||
("Atlantic/Azores", "Azores"),
|
||||
("Atlantic/Bermuda", "Bermuda"),
|
||||
("Atlantic/Canary", "Canary"),
|
||||
("Atlantic/Cape_Verde", "Cape_Verde"),
|
||||
("Atlantic/Faeroe", "Faeroe"),
|
||||
("Atlantic/Faroe", "Faroe"),
|
||||
("Atlantic/Jan_Mayen", "Jan_Mayen"),
|
||||
("Atlantic/Madeira", "Madeira"),
|
||||
("Atlantic/Reykjavik", "Reykjavik"),
|
||||
("Atlantic/South_Georgia", "South_Georgia"),
|
||||
("Atlantic/St_Helena", "St_Helena"),
|
||||
("Atlantic/Stanley", "Stanley"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Australia",
|
||||
[
|
||||
("Australia/ACT", "ACT"),
|
||||
("Australia/Adelaide", "Adelaide"),
|
||||
("Australia/Brisbane", "Brisbane"),
|
||||
("Australia/Broken_Hill", "Broken_Hill"),
|
||||
("Australia/Canberra", "Canberra"),
|
||||
("Australia/Currie", "Currie"),
|
||||
("Australia/Darwin", "Darwin"),
|
||||
("Australia/Eucla", "Eucla"),
|
||||
("Australia/Hobart", "Hobart"),
|
||||
("Australia/LHI", "LHI"),
|
||||
("Australia/Lindeman", "Lindeman"),
|
||||
("Australia/Lord_Howe", "Lord_Howe"),
|
||||
("Australia/Melbourne", "Melbourne"),
|
||||
("Australia/NSW", "NSW"),
|
||||
("Australia/North", "North"),
|
||||
("Australia/Perth", "Perth"),
|
||||
("Australia/Queensland", "Queensland"),
|
||||
("Australia/South", "South"),
|
||||
("Australia/Sydney", "Sydney"),
|
||||
("Australia/Tasmania", "Tasmania"),
|
||||
("Australia/Victoria", "Victoria"),
|
||||
("Australia/West", "West"),
|
||||
("Australia/Yancowinna", "Yancowinna"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Brazil",
|
||||
[
|
||||
("Brazil/Acre", "Acre"),
|
||||
("Brazil/DeNoronha", "DeNoronha"),
|
||||
("Brazil/East", "East"),
|
||||
("Brazil/West", "West"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Canada",
|
||||
[
|
||||
("Canada/Atlantic", "Atlantic"),
|
||||
("Canada/Central", "Central"),
|
||||
("Canada/Eastern", "Eastern"),
|
||||
("Canada/Mountain", "Mountain"),
|
||||
("Canada/Newfoundland", "Newfoundland"),
|
||||
("Canada/Pacific", "Pacific"),
|
||||
("Canada/Saskatchewan", "Saskatchewan"),
|
||||
("Canada/Yukon", "Yukon"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Chile",
|
||||
[
|
||||
("Chile/Continental", "Continental"),
|
||||
("Chile/EasterIsland", "EasterIsland"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Etc",
|
||||
[
|
||||
("Etc/Greenwich", "Greenwich"),
|
||||
("Etc/UCT", "UCT"),
|
||||
("Etc/UTC", "UTC"),
|
||||
("Etc/Universal", "Universal"),
|
||||
("Etc/Zulu", "Zulu"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Europe",
|
||||
[
|
||||
("Europe/Amsterdam", "Amsterdam"),
|
||||
("Europe/Andorra", "Andorra"),
|
||||
("Europe/Astrakhan", "Astrakhan"),
|
||||
("Europe/Athens", "Athens"),
|
||||
("Europe/Belfast", "Belfast"),
|
||||
("Europe/Belgrade", "Belgrade"),
|
||||
("Europe/Berlin", "Berlin"),
|
||||
("Europe/Bratislava", "Bratislava"),
|
||||
("Europe/Brussels", "Brussels"),
|
||||
("Europe/Bucharest", "Bucharest"),
|
||||
("Europe/Budapest", "Budapest"),
|
||||
("Europe/Busingen", "Busingen"),
|
||||
("Europe/Chisinau", "Chisinau"),
|
||||
("Europe/Copenhagen", "Copenhagen"),
|
||||
("Europe/Dublin", "Dublin"),
|
||||
("Europe/Gibraltar", "Gibraltar"),
|
||||
("Europe/Guernsey", "Guernsey"),
|
||||
("Europe/Helsinki", "Helsinki"),
|
||||
("Europe/Isle_of_Man", "Isle_of_Man"),
|
||||
("Europe/Istanbul", "Istanbul"),
|
||||
("Europe/Jersey", "Jersey"),
|
||||
("Europe/Kaliningrad", "Kaliningrad"),
|
||||
("Europe/Kiev", "Kiev"),
|
||||
("Europe/Kirov", "Kirov"),
|
||||
("Europe/Kyiv", "Kyiv"),
|
||||
("Europe/Lisbon", "Lisbon"),
|
||||
("Europe/Ljubljana", "Ljubljana"),
|
||||
("Europe/London", "London"),
|
||||
("Europe/Luxembourg", "Luxembourg"),
|
||||
("Europe/Madrid", "Madrid"),
|
||||
("Europe/Malta", "Malta"),
|
||||
("Europe/Mariehamn", "Mariehamn"),
|
||||
("Europe/Minsk", "Minsk"),
|
||||
("Europe/Monaco", "Monaco"),
|
||||
("Europe/Moscow", "Moscow"),
|
||||
("Europe/Nicosia", "Nicosia"),
|
||||
("Europe/Oslo", "Oslo"),
|
||||
("Europe/Paris", "Paris"),
|
||||
("Europe/Podgorica", "Podgorica"),
|
||||
("Europe/Prague", "Prague"),
|
||||
("Europe/Riga", "Riga"),
|
||||
("Europe/Rome", "Rome"),
|
||||
("Europe/Samara", "Samara"),
|
||||
("Europe/San_Marino", "San_Marino"),
|
||||
("Europe/Sarajevo", "Sarajevo"),
|
||||
("Europe/Saratov", "Saratov"),
|
||||
("Europe/Simferopol", "Simferopol"),
|
||||
("Europe/Skopje", "Skopje"),
|
||||
("Europe/Sofia", "Sofia"),
|
||||
("Europe/Stockholm", "Stockholm"),
|
||||
("Europe/Tallinn", "Tallinn"),
|
||||
("Europe/Tirane", "Tirane"),
|
||||
("Europe/Tiraspol", "Tiraspol"),
|
||||
("Europe/Ulyanovsk", "Ulyanovsk"),
|
||||
("Europe/Uzhgorod", "Uzhgorod"),
|
||||
("Europe/Vaduz", "Vaduz"),
|
||||
("Europe/Vatican", "Vatican"),
|
||||
("Europe/Vienna", "Vienna"),
|
||||
("Europe/Vilnius", "Vilnius"),
|
||||
("Europe/Volgograd", "Volgograd"),
|
||||
("Europe/Warsaw", "Warsaw"),
|
||||
("Europe/Zagreb", "Zagreb"),
|
||||
("Europe/Zaporozhye", "Zaporozhye"),
|
||||
("Europe/Zurich", "Zurich"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Indian",
|
||||
[
|
||||
("Indian/Antananarivo", "Antananarivo"),
|
||||
("Indian/Chagos", "Chagos"),
|
||||
("Indian/Christmas", "Christmas"),
|
||||
("Indian/Cocos", "Cocos"),
|
||||
("Indian/Comoro", "Comoro"),
|
||||
("Indian/Kerguelen", "Kerguelen"),
|
||||
("Indian/Mahe", "Mahe"),
|
||||
("Indian/Maldives", "Maldives"),
|
||||
("Indian/Mauritius", "Mauritius"),
|
||||
("Indian/Mayotte", "Mayotte"),
|
||||
("Indian/Reunion", "Reunion"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Mexico",
|
||||
[
|
||||
("Mexico/BajaNorte", "BajaNorte"),
|
||||
("Mexico/BajaSur", "BajaSur"),
|
||||
("Mexico/General", "General"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Other",
|
||||
[
|
||||
("CET", "CET"),
|
||||
("CST6CDT", "CST6CDT"),
|
||||
("Cuba", "Cuba"),
|
||||
("EET", "EET"),
|
||||
("EST", "EST"),
|
||||
("EST5EDT", "EST5EDT"),
|
||||
("Egypt", "Egypt"),
|
||||
("Eire", "Eire"),
|
||||
("GB", "GB"),
|
||||
("GB-Eire", "GB-Eire"),
|
||||
("Greenwich", "Greenwich"),
|
||||
("HST", "HST"),
|
||||
("Hongkong", "Hongkong"),
|
||||
("Iceland", "Iceland"),
|
||||
("Iran", "Iran"),
|
||||
("Israel", "Israel"),
|
||||
("Jamaica", "Jamaica"),
|
||||
("Japan", "Japan"),
|
||||
("Kwajalein", "Kwajalein"),
|
||||
("Libya", "Libya"),
|
||||
("MET", "MET"),
|
||||
("MST", "MST"),
|
||||
("MST7MDT", "MST7MDT"),
|
||||
("NZ", "NZ"),
|
||||
("NZ-CHAT", "NZ-CHAT"),
|
||||
("Navajo", "Navajo"),
|
||||
("PRC", "PRC"),
|
||||
("PST8PDT", "PST8PDT"),
|
||||
("Poland", "Poland"),
|
||||
("Portugal", "Portugal"),
|
||||
("ROC", "ROC"),
|
||||
("ROK", "ROK"),
|
||||
("Singapore", "Singapore"),
|
||||
("Turkey", "Turkey"),
|
||||
("UCT", "UCT"),
|
||||
("UTC", "UTC"),
|
||||
("Universal", "Universal"),
|
||||
("W-SU", "W-SU"),
|
||||
("WET", "WET"),
|
||||
("Zulu", "Zulu"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Pacific",
|
||||
[
|
||||
("Pacific/Apia", "Apia"),
|
||||
("Pacific/Auckland", "Auckland"),
|
||||
("Pacific/Bougainville", "Bougainville"),
|
||||
("Pacific/Chatham", "Chatham"),
|
||||
("Pacific/Chuuk", "Chuuk"),
|
||||
("Pacific/Easter", "Easter"),
|
||||
("Pacific/Efate", "Efate"),
|
||||
("Pacific/Enderbury", "Enderbury"),
|
||||
("Pacific/Fakaofo", "Fakaofo"),
|
||||
("Pacific/Fiji", "Fiji"),
|
||||
("Pacific/Funafuti", "Funafuti"),
|
||||
("Pacific/Galapagos", "Galapagos"),
|
||||
("Pacific/Gambier", "Gambier"),
|
||||
("Pacific/Guadalcanal", "Guadalcanal"),
|
||||
("Pacific/Guam", "Guam"),
|
||||
("Pacific/Honolulu", "Honolulu"),
|
||||
("Pacific/Johnston", "Johnston"),
|
||||
("Pacific/Kanton", "Kanton"),
|
||||
("Pacific/Kiritimati", "Kiritimati"),
|
||||
("Pacific/Kosrae", "Kosrae"),
|
||||
("Pacific/Kwajalein", "Kwajalein"),
|
||||
("Pacific/Majuro", "Majuro"),
|
||||
("Pacific/Marquesas", "Marquesas"),
|
||||
("Pacific/Midway", "Midway"),
|
||||
("Pacific/Nauru", "Nauru"),
|
||||
("Pacific/Niue", "Niue"),
|
||||
("Pacific/Norfolk", "Norfolk"),
|
||||
("Pacific/Noumea", "Noumea"),
|
||||
("Pacific/Pago_Pago", "Pago_Pago"),
|
||||
("Pacific/Palau", "Palau"),
|
||||
("Pacific/Pitcairn", "Pitcairn"),
|
||||
("Pacific/Pohnpei", "Pohnpei"),
|
||||
("Pacific/Ponape", "Ponape"),
|
||||
("Pacific/Port_Moresby", "Port_Moresby"),
|
||||
("Pacific/Rarotonga", "Rarotonga"),
|
||||
("Pacific/Saipan", "Saipan"),
|
||||
("Pacific/Samoa", "Samoa"),
|
||||
("Pacific/Tahiti", "Tahiti"),
|
||||
("Pacific/Tarawa", "Tarawa"),
|
||||
("Pacific/Tongatapu", "Tongatapu"),
|
||||
("Pacific/Truk", "Truk"),
|
||||
("Pacific/Wake", "Wake"),
|
||||
("Pacific/Wallis", "Wallis"),
|
||||
("Pacific/Yap", "Yap"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"US",
|
||||
[
|
||||
("US/Alaska", "Alaska"),
|
||||
("US/Aleutian", "Aleutian"),
|
||||
("US/Arizona", "Arizona"),
|
||||
("US/Central", "Central"),
|
||||
("US/East-Indiana", "East-Indiana"),
|
||||
("US/Eastern", "Eastern"),
|
||||
("US/Hawaii", "Hawaii"),
|
||||
("US/Indiana-Starke", "Indiana-Starke"),
|
||||
("US/Michigan", "Michigan"),
|
||||
("US/Mountain", "Mountain"),
|
||||
("US/Pacific", "Pacific"),
|
||||
("US/Samoa", "Samoa"),
|
||||
],
|
||||
),
|
||||
],
|
||||
default="Asia/Ho_Chi_Minh",
|
||||
max_length=50,
|
||||
verbose_name="location",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,84 +0,0 @@
|
|||
# Generated by Django 3.2.16 on 2022-11-16 15:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0136_alter_profile_timezone"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="PageVote",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"page",
|
||||
models.CharField(
|
||||
db_index=True, max_length=30, verbose_name="associated page"
|
||||
),
|
||||
),
|
||||
("score", models.IntegerField(default=0, verbose_name="votes")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "pagevote",
|
||||
"verbose_name_plural": "pagevotes",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="problemtranslation",
|
||||
name="language",
|
||||
field=models.CharField(
|
||||
choices=[("vi", "Vietnamese"), ("en", "English")],
|
||||
max_length=7,
|
||||
verbose_name="language",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PageVoteVoter",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("score", models.IntegerField()),
|
||||
(
|
||||
"pagevote",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="votes",
|
||||
to="judge.pagevote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"voter",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="voted_page",
|
||||
to="judge.profile",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "pagevote vote",
|
||||
"verbose_name_plural": "pagevote votes",
|
||||
"unique_together": {("voter", "pagevote")},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,73 +0,0 @@
|
|||
# Generated by Django 3.2.16 on 2022-11-17 17:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0137_auto_20221116_2201"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BookMark",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"page",
|
||||
models.CharField(
|
||||
db_index=True, max_length=30, verbose_name="associated page"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "bookmark",
|
||||
"verbose_name_plural": "bookmarks",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="MakeBookMark",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"bookmark",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="bookmark",
|
||||
to="judge.bookmark",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="user_bookmark",
|
||||
to="judge.profile",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "make bookmark",
|
||||
"verbose_name_plural": "make bookmarks",
|
||||
"unique_together": {("user", "bookmark")},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 3.2.16 on 2022-11-18 06:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("judge", "0138_bookmark_makebookmark"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="contest",
|
||||
name="freeze_after",
|
||||
field=models.DurationField(
|
||||
blank=True,
|
||||
help_text="Format hh:mm:ss. For example, if you want to freeze contest after 2 hours, enter 02:00:00",
|
||||
null=True,
|
||||
verbose_name="freeze after",
|
||||
),
|
||||
),
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue