Skip to content
# Generated by Django 5.0.7 on 2024-07-29 03:01
import django.contrib.auth.models
import django.core.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [('accounts', '0001_initial'), ('accounts', '0002_user_populate_migration'), ('accounts', '0003_user_schema_migration'), ('accounts', '0004_user_contenttypes_migration'), ('accounts', '0005_custom_user_cleanup'), ('accounts', '0006_empty_auth_user'), ('accounts', '0007_migrate_user_status_data'), ('accounts', '0008_inst_id_unique_and_updates')]
initial = True
dependencies = [
('auth', '0006_require_contenttypes_0002'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='customtemp_user_set', related_query_name='customtemp_user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='customtemp_user_set', related_query_name='customtemp_user', to='auth.permission', verbose_name='user permissions')),
],
options={
'abstract': False,
'verbose_name': 'user',
'verbose_name_plural': 'users',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.AlterField(
model_name='user',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups'),
),
migrations.AlterField(
model_name='user',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'),
),
migrations.AddField(
model_name='user',
name='editor_mode',
field=models.CharField(choices=[('default', 'Default'), ('sublime', 'Sublime text'), ('emacs', 'Emacs'), ('vim', 'Vim')], default='default', help_text="Which key bindings you prefer when editing larger amounts of text or code. (If you do not understand what this means, leave it as 'Default'.)", max_length=20, verbose_name='Editor mode'),
),
migrations.AddField(
model_name='user',
name='institutional_id',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Institutional ID'),
),
migrations.AddField(
model_name='user',
name='institutional_id_verified',
field=models.BooleanField(default=False, help_text="Indicates that this user's institutional ID has been verified as being associated with the individual able to log in to this account.", verbose_name='Institutional ID verified'),
),
migrations.AddField(
model_name='user',
name='key_time',
field=models.DateTimeField(blank=True, default=None, help_text='The time stamp of the sign in token.', null=True, verbose_name='Key time'),
),
migrations.AddField(
model_name='user',
name='name_verified',
field=models.BooleanField(default=False, help_text="Indicates that this user's name has been verified as being associated with the individual able to sign in to this account.", verbose_name='Name verified'),
),
migrations.AddField(
model_name='user',
name='sign_in_key',
field=models.CharField(blank=True, db_index=True, help_text='The sign in token sent out in email.', max_length=50, null=True, unique=True, verbose_name='Sign in key'),
),
migrations.AddField(
model_name='user',
name='status',
field=models.CharField(choices=[('unconfirmed', 'Unconfirmed'), ('active', 'Active')], max_length=50, null=True, verbose_name='User status'),
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(blank=True, max_length=100, verbose_name='email address'),
),
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=100, verbose_name='first name'),
),
migrations.AlterField(
model_name='user',
name='last_name',
field=models.CharField(blank=True, max_length=100, verbose_name='last name'),
),
migrations.AlterField(
model_name='user',
name='editor_mode',
field=models.CharField(choices=[('default', 'Default'), ('sublime', 'Sublime text'), ('emacs', 'Emacs'), ('vim', 'Vim')], default='default', help_text="Which key bindings you prefer when editing larger amounts of text or code. (If you do not understand what this means, leave it as 'Default'.)", max_length=20, verbose_name='Editor mode'),
),
migrations.AlterField(
model_name='user',
name='institutional_id',
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Institutional ID'),
),
migrations.AlterField(
model_name='user',
name='status',
field=models.CharField(choices=[('unconfirmed', 'Unconfirmed'), ('active', 'Active')], max_length=50, null=True, verbose_name='User status'),
),
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
from django.db import models, migrations
from relate.utils import ignore_no_such_table
def forwards(apps, schema_editor):
ignore_no_such_table(populate_table,
apps, schema_editor,
"auth", "User",
"accounts", "User")
ignore_no_such_table(populate_table,
apps, schema_editor,
"auth", "User_groups",
"accounts", "User_groups")
ignore_no_such_table(populate_table,
apps, schema_editor,
"auth", "User_user_permissions",
"accounts", "User_user_permissions")
def backwards(apps, schema_editor):
empty_table(apps, schema_editor,
"accounts", "User_user_permissions")
empty_table(apps, schema_editor,
"accounts", "User_groups")
empty_table(apps, schema_editor,
"accounts", "User")
def populate_table(apps, schema_editor, from_app, from_model, to_app, to_model):
# Due to swapped out models, which means that some model classes (and/or
# their auto-created M2M tables) do not exist or don't function correctly,
# it is better to use SELECT / INSERT than attempting to use ORM.
import math
from_table_name = make_table_name(apps, from_app, from_model)
to_table_name = make_table_name(apps, to_app, to_model)
max_id = get_max_id(schema_editor, from_table_name)
# Use batches to avoid loading entire table into memory
BATCH_SIZE = 100
# Careful with off-by-one errors where max_id is a multiple of BATCH_SIZE
for batch_num in range(0, int(math.floor(max_id / BATCH_SIZE)) + 1):
start = batch_num * BATCH_SIZE
stop = start + BATCH_SIZE
ops = schema_editor.connection.ops
old_rows, old_cols = fetch_with_column_names(schema_editor,
"SELECT * FROM {0} WHERE id >= %s AND id < %s;".format(
ops.quote_name(from_table_name)),
[start, stop])
# The column names in the new table aren't necessarily the same
# as in the old table - things like 'user_id' vs 'myuser_id'.
# We have to map them, and this seems to be good enough for our needs:
base_from_model = from_model.split("_")[0]
base_to_model = to_model.split("_")[0]
map_fk_col = lambda c: "{0}_id".format(base_to_model).lower() if c == "{0}_id".format(base_from_model).lower() else c
new_cols = list(map(map_fk_col, old_cols))
for row in old_rows:
values_sql = ", ".join(["%s"] * len(new_cols))
columns_sql = ", ".join(ops.quote_name(col_name) for col_name in new_cols)
sql = "INSERT INTO {0} ({1}) VALUES ({2});".format(ops.quote_name(to_table_name),
columns_sql,
values_sql)
# could collect and do 'executemany', but sqlite doesn't let us
# execute more than one statement at once it seems.
schema_editor.execute(sql, row)
reset_sequence(apps, schema_editor, to_app, to_model)
def empty_table(apps, schema_editor, from_app, from_model):
from_table_name = make_table_name(apps, from_app, from_model)
ops = schema_editor.connection.ops
schema_editor.execute("DELETE FROM {0};".format(ops.quote_name(from_table_name)))
def make_table_name(apps, app, model):
try:
m = apps.get_model(app, model)
if m._meta.db_table:
return m._meta.db_table
except LookupError:
pass # for M2M fields
return "{0}_{1}".format(app, model).lower()
def fetch_with_column_names(schema_editor, sql, params):
c = schema_editor.connection.cursor()
c.execute(sql, params)
rows = c.fetchall()
return rows, [r[0] for r in c.description]
def get_max_id(schema_editor, table_name):
max_id = fetch_with_column_names(schema_editor, "SELECT MAX(id) FROM {0};".format(table_name), [])[0][0][0]
if max_id is None:
max_id = 0
return max_id
def reset_sequence(apps, schema_editor, app, model):
if schema_editor.connection.vendor == 'postgresql':
table_name = make_table_name(apps, app, model)
sequence_name = "{0}_id_seq".format(table_name)
schema_editor.execute("SELECT setval(%s, %s, false);", [sequence_name, get_max_id(schema_editor, table_name) + 1])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RunPython(forwards, backwards),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
from django.db import models, migrations
def forwards(apps, schema_editor):
change_foreign_keys(apps, schema_editor,
"auth", "User",
"accounts", "User")
def backwards(apps, schema_editor):
change_foreign_keys(apps, schema_editor,
"accounts", "User",
"auth", "User")
def change_foreign_keys(apps, schema_editor, from_app, from_model_name, to_app, to_model_name):
FromModel = apps.get_model(from_app, from_model_name)
ToModel = apps.get_model(to_app, to_model_name)
# We don't make assumptions about which model is being pointed to by
# AUTH_USER_MODEL. So include fields from both FromModel and ToModel.
# Only one of them will actually have FK fields pointing to them.
import sys
if sys.version_info >= (3,):
from warnings import warn
warn("This migration seems to only work on Py2, as of Django 1.9")
print()
fields = FromModel._meta.get_fields(include_hidden=True) + ToModel._meta.get_fields(include_hidden=True)
for rel in fields:
if not hasattr(rel, 'field') or not isinstance(rel.field, models.ForeignKey):
continue
fk_field = rel.field
f_name, f_field_name, pos_args, kw_args = fk_field.deconstruct()
# fk_field might have been the old or new one. We need to fix things up.
old_field_kwargs = kw_args.copy()
old_field_kwargs['to'] = FromModel
old_field = fk_field.__class__(*pos_args, **old_field_kwargs)
old_field.model = fk_field.model
new_field_kwargs = kw_args.copy()
new_field_kwargs['to'] = ToModel
new_field = fk_field.__class__(*pos_args, **new_field_kwargs)
new_field.model = fk_field.model
if fk_field.model._meta.auto_created:
# If this is a FK that is part of an M2M on the model itself,
# we've already dealt with this, by virtue of the data migration
# that populates the auto-created M2M field.
if fk_field.model._meta.auto_created in [ToModel, FromModel]:
print("Skipping {0}".format(repr(rel)))
continue
# In this case (FK fields that are part of an autogenerated M2M),
# the column name in the new M2M might be different to that in the
# old M2M. This makes things much harder, and involves duplicating
# some Django logic.
# Build a correct ForeignKey field, as it should
# have been on FromModel
old_field.name = from_model_name.lower()
old_field.column = "{0}_id".format(old_field.name)
# build a correct ForeignKey field, as it should
# be on ToModel
new_field.name = to_model_name.lower()
new_field.column = "{0}_id".format(new_field.name)
else:
old_field.name = fk_field.name
old_field.column = fk_field.column
new_field.name = fk_field.name
new_field.column = fk_field.column
show = lambda m: "{0}.{1}".format(m._meta.app_label, m.__name__)
print("Fixing FK in {0}, col {1} -> {2}, from {3} -> {4}".format(
show(fk_field.model),
old_field.column, new_field.column,
show(old_field.remote_field.to), show(new_field.remote_field.to),
))
schema_editor.alter_field(fk_field.model, old_field, new_field, strict=True)
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_user_populate_migration'),
('admin', '0001_initial'),
('auth', '0006_require_contenttypes_0002'),
('course', '0075_course_metadata'),
]
operations = [
migrations.RunPython(forwards, backwards),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def forwards(apps, schema_editor):
fix_contenttype(apps, schema_editor,
"auth", "User",
"accounts", "User")
def backwards(apps, schema_editor):
fix_contenttype(apps, schema_editor,
"accounts", "User",
"auth", "User")
def fix_contenttype(apps, schema_editor, from_app, from_model, to_app, to_model):
from_model, to_model = from_model.lower(), to_model.lower()
schema_editor.execute("UPDATE django_content_type SET app_label=%s, model=%s WHERE app_label=%s AND model=%s;",
[to_app, to_model, from_app, from_model])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_user_schema_migration'),
]
operations = [
migrations.RunPython(forwards, backwards),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_user_contenttypes_migration'),
]
operations = [
migrations.AlterField(
model_name='user',
name='groups',
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
),
migrations.AlterField(
model_name='user',
name='user_permissions',
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from relate.utils import ignore_no_such_table
def forwards(apps, schema_editor):
ignore_no_such_table(empty_table,
apps, schema_editor,
"auth", "User_user_permissions")
ignore_no_such_table(empty_table,
apps, schema_editor,
"auth", "User_groups")
ignore_no_such_table(empty_table,
apps, schema_editor,
"auth", "User")
def backwards(apps, schema_editor):
populate_table(apps, schema_editor,
"accounts", "User",
"auth", "User")
populate_table(apps, schema_editor,
"accounts", "User_groups",
"auth", "User_groups")
populate_table(apps, schema_editor,
"accounts", "User_user_permissions",
"auth", "User_user_permissions")
def populate_table(apps, schema_editor, from_app, from_model, to_app, to_model):
# Due to swapped out models, which means that some model classes (and/or
# their auto-created M2M tables) do not exist or don't function correctly,
# it is better to use SELECT / INSERT than attempting to use ORM.
import math
from_table_name = make_table_name(apps, from_app, from_model)
to_table_name = make_table_name(apps, to_app, to_model)
max_id = get_max_id(schema_editor, from_table_name)
# Use batches to avoid loading entire table into memory
BATCH_SIZE = 100
# Careful with off-by-one errors where max_id is a multiple of BATCH_SIZE
for batch_num in range(0, int(math.floor(max_id / BATCH_SIZE)) + 1):
start = batch_num * BATCH_SIZE
stop = start + BATCH_SIZE
ops = schema_editor.connection.ops
old_rows, old_cols = fetch_with_column_names(schema_editor,
"SELECT * FROM {0} WHERE id >= %s AND id < %s;".format(
ops.quote_name(from_table_name)),
[start, stop])
# The column names in the new table aren't necessarily the same
# as in the old table - things like 'user_id' vs 'myuser_id'.
# We have to map them, and this seems to be good enough for our needs:
base_from_model = from_model.split("_")[0]
base_to_model = to_model.split("_")[0]
map_fk_col = lambda c: "{0}_id".format(base_to_model).lower() if c == "{0}_id".format(base_from_model).lower() else c
new_cols = list(map(map_fk_col, old_cols))
for row in old_rows:
values_sql = ", ".join(["%s"] * len(new_cols))
columns_sql = ", ".join(ops.quote_name(col_name) for col_name in new_cols)
sql = "INSERT INTO {0} ({1}) VALUES ({2});".format(ops.quote_name(to_table_name),
columns_sql,
values_sql)
# could collect and do 'executemany', but sqlite doesn't let us
# execute more than one statement at once it seems.
schema_editor.execute(sql, row)
reset_sequence(apps, schema_editor, to_app, to_model)
def empty_table(apps, schema_editor, from_app, from_model):
from_table_name = make_table_name(apps, from_app, from_model)
ops = schema_editor.connection.ops
schema_editor.execute("DELETE FROM {0};".format(ops.quote_name(from_table_name)))
def make_table_name(apps, app, model):
try:
m = apps.get_model(app, model)
if m._meta.db_table:
return m._meta.db_table
except LookupError:
pass # for M2M fields
return "{0}_{1}".format(app, model).lower()
def fetch_with_column_names(schema_editor, sql, params):
c = schema_editor.connection.cursor()
c.execute(sql, params)
rows = c.fetchall()
return rows, [r[0] for r in c.description]
def get_max_id(schema_editor, table_name):
max_id = fetch_with_column_names(schema_editor, "SELECT MAX(id) FROM {0};".format(table_name), [])[0][0][0]
if max_id is None:
max_id = 0
return max_id
def reset_sequence(apps, schema_editor, app, model):
if schema_editor.connection.vendor == 'postgresql':
table_name = make_table_name(apps, app, model)
sequence_name = "{0}_id_seq".format(table_name)
schema_editor.execute("SELECT setval(%s, %s, false);", [sequence_name, get_max_id(schema_editor, table_name) + 1])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_custom_user_cleanup'),
]
operations = [
migrations.RunPython(forwards, backwards),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-01-12 02:48
from __future__ import unicode_literals
from django.db import migrations, models
def forwards(apps, schema_editor):
UserStatus = apps.get_model("course", "UserStatus")
for ustatus in UserStatus.objects.all().select_related("user"):
user = ustatus.user
user.editor_mode = ustatus.editor_mode
user.key_time = ustatus.key_time
user.sign_in_key = ustatus.sign_in_key
user.status = ustatus.status
user.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_empty_auth_user'),
]
operations = [
migrations.AddField(
model_name='user',
name='editor_mode',
field=models.CharField(choices=[(b'default', 'Default'), (b'sublime', b'Sublime text'), (b'emacs', b'Emacs'), (b'vim', b'Vim')], default=b'default', help_text="Which key bindings you prefer when editing larger amounts of text or code. (If you do not understand what this means, leave it as 'Default'.)", max_length=20, verbose_name='Editor mode'),
),
migrations.AddField(
model_name='user',
name='institutional_id',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Institutional ID'),
),
migrations.AddField(
model_name='user',
name='institutional_id_verified',
field=models.BooleanField(default=False, help_text="Indicates that this user's institutional ID has been verified as being associated with the individual able to log in to this account.", verbose_name='Institutional ID verified'),
),
migrations.AddField(
model_name='user',
name='key_time',
field=models.DateTimeField(default=None, help_text='The time stamp of the sign in token.', verbose_name='Key time', null=True, blank=True),
),
migrations.AddField(
model_name='user',
name='name_verified',
field=models.BooleanField(default=False, help_text="Indicates that this user's name has been verified as being associated with the individual able to sign in to this account.", verbose_name='Name verified'),
),
migrations.AddField(
model_name='user',
name='sign_in_key',
field=models.CharField(blank=True, db_index=True, help_text='The sign in token sent out in email.', max_length=50, null=True, unique=True, verbose_name='Sign in key'),
),
migrations.AddField(
model_name='user',
name='status',
field=models.CharField(choices=[(b'unconfirmed', 'Unconfirmed'), (b'active', 'Active')], max_length=50, null=True, verbose_name='User status'),
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(blank=True, max_length=100, verbose_name='email address'),
),
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=100, verbose_name='first name'),
),
migrations.AlterField(
model_name='user',
name='last_name',
field=models.CharField(blank=True, max_length=100, verbose_name='last name'),
),
migrations.RunPython(forwards),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.9.1 on 2016-01-15 15:10
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_migrate_user_status_data'),
]
operations = [
migrations.AlterField(
model_name='user',
name='editor_mode',
field=models.CharField(choices=[('default', 'Default'), ('sublime', 'Sublime text'), ('emacs', 'Emacs'), ('vim', 'Vim')], default='default', help_text="Which key bindings you prefer when editing larger amounts of text or code. (If you do not understand what this means, leave it as 'Default'.)", max_length=20, verbose_name='Editor mode'),
),
migrations.AlterField(
model_name='user',
name='institutional_id',
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Institutional ID'),
),
migrations.AlterField(
model_name='user',
name='status',
field=models.CharField(choices=[('unconfirmed', 'Unconfirmed'), ('active', 'Active')], max_length=50, null=True, verbose_name='User status'),
),
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-09-12 04:03
from __future__ import unicode_literals
from django.db import migrations, models
......
# Generated by Django 1.10.4 on 2017-04-12 08:55
import django.contrib.auth.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_user_git_auth_token_hash'),
]
operations = [
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
),
]
# Generated by Django 1.11.5 on 2017-09-21 03:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0010_change_username_validator'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='git_auth_token_hash',
),
]
# Generated by Django 3.2.2 on 2021-05-07 22:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0011_remove_user_git_auth_token_hash'),
]
operations = [
migrations.AlterField(
model_name='user',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]
# Generated by Django 4.2.5 on 2023-09-24 19:50
import django.contrib.auth.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0012_django32_bigauto'),
]
operations = [
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(blank=True, max_length=200, verbose_name='email address'),
),
migrations.AlterField(
model_name='user',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. Letters, digits and @/./+/-/_ only.', max_length=200, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
),
]
# Generated by Django 4.2.14 on 2024-07-24 19:16
from django.db import migrations, models
def switch_sublime_to_default(apps, schema_editor):
User = apps.get_model("accounts", "User")
User.objects.filter(editor_mode="sublime").update(editor_mode="default")
class Migration(migrations.Migration):
dependencies = [
('accounts', '0013_alter_user_email_alter_user_username'),
]
operations = [
migrations.RunPython(switch_sublime_to_default),
migrations.AlterField(
model_name='user',
name='editor_mode',
field=models.CharField(choices=[('default', 'Default'), ('emacs', 'Emacs'), ('vim', 'Vim')], default='default', help_text="Which key bindings you prefer when editing larger amounts of text or code. (If you do not understand what this means, leave it as 'Default'.)", max_length=20, verbose_name='Editor mode'),
),
]
# -*- coding: utf-8 -*-
from __future__ import annotations
from __future__ import division, unicode_literals
__copyright__ = "Copyright (C) 2014 Andreas Kloeckner"
......@@ -25,12 +24,15 @@ THE SOFTWARE.
"""
from django.contrib.auth.models import (
AbstractBaseUser,
PermissionsMixin,
UserManager,
)
from django.contrib.auth.validators import ASCIIUsernameValidator
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.utils.translation import ugettext_lazy as _, pgettext_lazy
from django.utils import timezone
from django.contrib.auth.models import UserManager
from django.core import validators
from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy
from course.constants import USER_STATUS_CHOICES
......@@ -39,79 +41,73 @@ from course.constants import USER_STATUS_CHOICES
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(
_('username'),
max_length=30,
_("username"),
max_length=200,
unique=True,
help_text=_(
'Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[
validators.RegexValidator(
r'^[\w.@+-]+$',
_('Enter a valid username. This value may contain only '
'letters, numbers ' 'and @/./+/-/_ characters.')
),
],
"Required. Letters, digits and @/./+/-/_ only."),
validators=[ASCIIUsernameValidator()],
error_messages={
'unique': _("A user with that username already exists."),
"unique": _("A user with that username already exists."),
},
)
first_name = models.CharField(_('first name'), max_length=100, blank=True)
last_name = models.CharField(_('last name'), max_length=100, blank=True)
email = models.EmailField(_('email address'), blank=True,
max_length=100)
first_name = models.CharField(_("first name"), max_length=100, blank=True)
last_name = models.CharField(_("last name"), max_length=100, blank=True)
email = models.EmailField(_("email address"), blank=True,
max_length=200)
name_verified = models.BooleanField(
_('Name verified'),
_("Name verified"),
default=False,
help_text=_(
'Indicates that this user\'s name has been verified '
'as being associated with the individual able to sign '
'in to this account.'
"Indicates that this user's name has been verified "
"as being associated with the individual able to sign "
"in to this account."
),
)
is_active = models.BooleanField(
pgettext_lazy("User status", "active"),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = UserManager()
is_staff = models.BooleanField(
_('staff status'),
_("staff status"),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
help_text=_("Designates whether the user can log into this admin site."),
)
institutional_id = models.CharField(max_length=100,
verbose_name=_('Institutional ID'),
verbose_name=_("Institutional ID"),
blank=True, null=True, unique=True, db_index=True)
institutional_id_verified = models.BooleanField(
_('Institutional ID verified'),
_("Institutional ID verified"),
default=False,
help_text=_(
'Indicates that this user\'s institutional ID has been verified '
'as being associated with the individual able to log '
'in to this account.'
"Indicates that this user's institutional ID has been verified "
"as being associated with the individual able to log "
"in to this account."
),
)
status = models.CharField(max_length=50,
choices=USER_STATUS_CHOICES,
verbose_name=_('User status'),
verbose_name=_("User status"),
null=True)
sign_in_key = models.CharField(max_length=50,
help_text=_("The sign in token sent out in email."),
null=True, unique=True, db_index=True, blank=True,
# Translators: the sign in token of the user.
verbose_name=_('Sign in key'))
verbose_name=_("Sign in key"))
key_time = models.DateTimeField(default=None,
null=True, blank=True,
help_text=_("The time stamp of the sign in token."),
# Translators: the time when the token is sent out.
verbose_name=_('Key time'))
verbose_name=_("Key time"))
editor_mode = models.CharField(max_length=20,
help_text=_("Which key bindings you prefer when editing "
......@@ -120,7 +116,6 @@ class User(AbstractBaseUser, PermissionsMixin):
"leave it as 'Default'.)"),
choices=(
("default", _("Default")),
("sublime", "Sublime text"),
("emacs", "Emacs"),
("vim", "Vim"),
),
......@@ -128,100 +123,130 @@ class User(AbstractBaseUser, PermissionsMixin):
# Translators: the text editor used by participants
verbose_name=_("Editor mode"))
git_auth_token_hash = models.CharField(max_length=200,
help_text=_("A hash of the authentication token to be "
"used for direct git access."),
null=True, blank=True,
verbose_name=_('Hash of git authentication token'))
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
verbose_name = _("user")
verbose_name_plural = _("users")
def get_full_name(self, allow_blank=True, force_verbose_blank=False):
def get_full_name(
self, allow_blank=True, force_verbose_blank=False
) -> str | None:
if (not allow_blank
and not self.first_name or not self.last_name):
and (not self.first_name or not self.last_name)):
return None
def verbose_blank(s):
def verbose_blank(s: str) -> str:
if force_verbose_blank:
if not s:
return _("(blank)")
return gettext("(blank)")
else:
return s
return s
def default_fullname(first_name, last_name):
def default_fullname(first_name: str, last_name: str) -> str:
"""
Returns the first_name plus the last_name, with a space in
between.
"""
if force_verbose_blank:
first_name
return '%s %s' % (
verbose_blank(first_name), verbose_blank(last_name))
return f"{verbose_blank(first_name)} {verbose_blank(last_name)}"
from django.conf import settings
format_method = getattr(
settings,
"RELATE_USER_FULL_NAME_FORMAT_METHOD",
default_fullname)
from accounts.utils import relate_user_method_settings
format_method = relate_user_method_settings.custom_full_name_method
if format_method is None:
format_method = default_fullname
try:
full_name = format_method(
verbose_blank(self.first_name), verbose_blank(self.last_name))
except:
except Exception:
full_name = default_fullname(
verbose_blank(self.first_name), verbose_blank(self.last_name))
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
def get_masked_profile(self) -> str:
"""
Returns the masked user profile.
"""
def default_mask_method(user):
return "{}{}".format(_("User"), str(user.pk))
from accounts.utils import relate_user_method_settings
mask_method = relate_user_method_settings.custom_profile_mask_method
if mask_method is None:
mask_method = default_mask_method
# Intentionally don't fallback if it failed -- let user see the exception.
result = mask_method(self)
if not result:
raise RuntimeError("get_masked_profile should not return None.")
else:
result = str(result).strip()
if not result:
raise RuntimeError("get_masked_profile should not return "
"an empty string.")
return result
def get_short_name(self) -> str:
"""Returns the short name for the user."""
return self.first_name
def get_email_appellation(self):
"Return the appellation of the receiver in email."
from django.conf import settings
# import the user defined priority list
customized_priority_list = getattr(
settings,
"RELATE_EMAIL_APPELATION_PRIORITY_LIST", [])
def get_email_appellation(self) -> str:
"""Return the appellation of the receiver in email."""
priority_list = []
# filter out not allowd appellations in customized list
for e in customized_priority_list:
if e in ["first_name", "email", "username", "full_name"]:
priority_list.append(e)
# make sure the default appellations are included in case
# user defined appellations are not available.
for e in ["first_name", "email", "username"]:
if e not in priority_list:
priority_list.append(e)
from accounts.utils import relate_user_method_settings
priority_list = (
relate_user_method_settings.email_appellation_priority_list)
for attr in priority_list:
if attr == "full_name":
appellation = self.get_full_name(allow_blank=True)
appellation = self.get_full_name(allow_blank=False)
else:
appellation = getattr(self, attr)
if appellation:
return appellation
else:
if not appellation:
continue
return _("user")
return appellation
return gettext("user")
def clean(self) -> None:
super().clean()
# email can be None in Django admin when create new user
if self.email is not None:
self.email = self.email.strip()
if self.email:
qset = self.__class__.objects.filter(email__iexact=self.email)
if self.pk is not None:
# In case editing an existing user object
qset = qset.exclude(pk=self.pk)
if qset.exists():
from django.core.exceptions import ValidationError
raise ValidationError(
{"email": _("That email address is already in use.")})
def save(self, *args, **kwargs):
update_fields = kwargs.get("update_fields")
# This is for backward compatibility.
# Because user instances are frequently updated when auth_login,
# reset_password. Without this, no user will be able to login.
if ((update_fields is not None and "email" in update_fields)
or self.pk is None):
self.clean()
if self.institutional_id is not None:
self.institutional_id = self.institutional_id.strip()
# works around https://code.djangoproject.com/ticket/4136#comment:33
self.institutional_id = self.institutional_id or None
super(User, self).save(*args, **kwargs)
super().save(*args, **kwargs)
# }}}
......
from django.test import TestCase
from django.test import TestCase # noqa
# Create your tests here.
from __future__ import annotations
__copyright__ = "Copyright (C) 2018 Dong Zhuang"
__license__ = """
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from course.constants import DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST
from relate.checks import INSTANCE_ERROR_PATTERN, RelateCriticalCheckMessage, Warning
RELATE_USER_FULL_NAME_FORMAT_METHOD = "RELATE_USER_FULL_NAME_FORMAT_METHOD"
RELATE_EMAIL_APPELLATION_PRIORITY_LIST = (
"RELATE_EMAIL_APPELLATION_PRIORITY_LIST")
RELATE_USER_PROFILE_MASK_METHOD = "RELATE_USER_PROFILE_MASK_METHOD"
class RelateUserMethodSettingsInitializer:
"""
This is used to check (validate) settings.RELATE_CSV_SETTINGS (optional)
and initialize the settings for csv export for csv-related forms.
"""
def __init__(self) -> None:
self._custom_full_name_method = None
self._email_appellation_priority_list = (
DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST)
self._custom_profile_mask_method = None
@cached_property
def custom_full_name_method(self):
self.check_custom_full_name_method()
return self._custom_full_name_method
@cached_property
def email_appellation_priority_list(self):
self.check_email_appellation_priority_list()
return self._email_appellation_priority_list
@cached_property
def custom_profile_mask_method(self):
self.check_user_profile_mask_method()
return self._custom_profile_mask_method
def check_user_profile_mask_method(self):
self._custom_profile_mask_method = None
errors = []
from django.conf import settings
custom_user_profile_mask_method = getattr(
settings, RELATE_USER_PROFILE_MASK_METHOD, None)
if custom_user_profile_mask_method is None:
return errors
if isinstance(custom_user_profile_mask_method, str):
try:
custom_user_profile_mask_method = (
import_string(custom_user_profile_mask_method))
except ImportError:
errors = [RelateCriticalCheckMessage(
msg=(
f"{RELATE_USER_PROFILE_MASK_METHOD}: "
f"`{custom_user_profile_mask_method}` failed to be imported. "
),
id="relate_user_profile_mask_method.E001"
)]
return errors
self._custom_profile_mask_method = custom_user_profile_mask_method
if not callable(custom_user_profile_mask_method):
errors.append(RelateCriticalCheckMessage(
msg=(
f"{RELATE_USER_PROFILE_MASK_METHOD}: "
f"`{custom_user_profile_mask_method}` is not a callable."
),
id="relate_user_profile_mask_method.E002"
))
else:
import inspect
sig = inspect.signature(custom_user_profile_mask_method)
n_args = len([p.name for p in sig.parameters.values()])
if not n_args or n_args > 1:
errors.append(RelateCriticalCheckMessage(
msg=(
"%(location)s: `%(method)s` should have exactly "
"one arg, got %(n)d."
% {"location": RELATE_USER_PROFILE_MASK_METHOD,
"method": custom_user_profile_mask_method,
"n": n_args
}
),
id="relate_user_profile_mask_method.E003"
))
if errors:
self._custom_profile_mask_method = None
return errors
def check_email_appellation_priority_list(self):
errors = []
self._email_appellation_priority_list = (
DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST)
from django.conf import settings
custom_email_appellation_priority_list = getattr(
settings, RELATE_EMAIL_APPELLATION_PRIORITY_LIST, None)
if not custom_email_appellation_priority_list:
if hasattr(settings, "RELATE_EMAIL_APPELATION_PRIORITY_LIST"):
if settings.RELATE_EMAIL_APPELATION_PRIORITY_LIST is not None:
errors.append(Warning(
msg=("'RELATE_EMAIL_APPELATION_PRIORITY_LIST' is "
"deprecated due to typo, use "
"'RELATE_EMAIL_APPELLATION_PRIORITY_LIST' "
"instead."),
id="relate_email_appellation_priority_list.W003"))
custom_email_appellation_priority_list = (
settings.RELATE_EMAIL_APPELATION_PRIORITY_LIST)
if not custom_email_appellation_priority_list:
return errors
if not isinstance(custom_email_appellation_priority_list, list | tuple):
errors.append(Warning(
msg=("{}, {}".format(
INSTANCE_ERROR_PATTERN
% {"location": RELATE_EMAIL_APPELLATION_PRIORITY_LIST,
"types": "list or tuple"},
f"default value '{DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST!r}' "
"will be used")),
id="relate_email_appellation_priority_list.W001"))
return errors
priority_list = []
not_supported_appels = []
# filter out not allowed appellations in customized list
for appell in custom_email_appellation_priority_list:
if appell in DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST:
priority_list.append(appell)
else:
not_supported_appels.append(appell)
# make sure the default appellations are included in case
# user defined appellations are not available.
for appell in DEFAULT_EMAIL_APPELLATION_PRIORITY_LIST:
if appell not in priority_list:
priority_list.append(appell)
assert len(priority_list)
self._email_appellation_priority_list = priority_list
if not_supported_appels:
errors.append(Warning(
msg=("{location}: not supported email appelation(s) found "
"and will be ignored: {not_supported_appelds}. "
"{actual} will be used as "
"relate_email_appellation_priority_list.".format(
location=RELATE_EMAIL_APPELLATION_PRIORITY_LIST,
not_supported_appelds=", ".join(not_supported_appels),
actual=repr(priority_list),
)),
id="relate_email_appellation_priority_list.W002"))
return errors
def check_custom_full_name_method(self):
self._custom_full_name_method = None
errors = []
from django.conf import settings
relate_user_full_name_format_method = getattr(
settings, RELATE_USER_FULL_NAME_FORMAT_METHOD, None)
if relate_user_full_name_format_method is None:
return errors
if isinstance(relate_user_full_name_format_method, str):
try:
relate_user_full_name_format_method = (
import_string(relate_user_full_name_format_method))
except ImportError:
errors = [Warning(
msg=(
f"{RELATE_USER_FULL_NAME_FORMAT_METHOD}: "
f"`{relate_user_full_name_format_method}` "
"failed to be imported, "
"default format method will be used."
),
id="relate_user_full_name_format_method.W001"
)]
return errors
self._custom_full_name_method = relate_user_full_name_format_method
if not callable(relate_user_full_name_format_method):
errors.append(Warning(
msg=(
f"{RELATE_USER_FULL_NAME_FORMAT_METHOD}: "
f"`{relate_user_full_name_format_method}` is not a callable, "
"default format method will be used."
),
id="relate_user_full_name_format_method.W002"
))
else:
try:
returned_name = (
relate_user_full_name_format_method("first_name",
"last_name"))
except Exception as e:
from traceback import format_exc
errors.append(Warning(
msg=(
f"{RELATE_USER_FULL_NAME_FORMAT_METHOD}: "
f"`{relate_user_full_name_format_method}` called with '"
"args 'first_name', 'last_name' failed with"
"exception below:\n"
f"{type(e).__name__}: {e!s}\n"
f"{format_exc()}\n\n"
"Default format method will be used."
),
id="relate_user_full_name_format_method.W003"
))
else:
unexpected_return_value = ""
if returned_name is None:
unexpected_return_value = "None"
if not isinstance(returned_name, str):
unexpected_return_value = type(returned_name).__name__
elif not returned_name.strip():
unexpected_return_value = f"empty string {returned_name}"
if unexpected_return_value:
errors.append(Warning(
msg=(f"{RELATE_USER_FULL_NAME_FORMAT_METHOD}: "
f"`{relate_user_full_name_format_method}` is expected to "
"return a non-empty string, "
f"got `{unexpected_return_value}`, "
"default format method will be used."),
id="relate_user_full_name_format_method.W004"
))
else:
returned_name2 = (
relate_user_full_name_format_method("first_name2",
"last_name2"))
if returned_name == returned_name2:
errors.append(Warning(
msg=(f"{RELATE_USER_FULL_NAME_FORMAT_METHOD}: "
f"`{relate_user_full_name_format_method}` "
"is expected to "
"return different value with different "
"input, default format method will be used."),
id="relate_user_full_name_format_method.W005"
))
if errors:
self._custom_full_name_method = None
return errors
relate_user_method_settings = RelateUserMethodSettingsInitializer()
from django.shortcuts import render
# from django.shortcuts import render
# Create your views here.
#! /bin/bash
set -e
rm -Rf pyz-build
mkdir pyz-build
function pywhichmod()
{
python -c "import $1; import os.path; print($1.__file__.replace('pyc', 'py'))"
}
function pywhichpkg()
{
python -c "import $1; import os.path; print(os.path.dirname($1.__file__))"
}
pyzzer.pyz course relate -r \
$(pywhichmod six) \
$(pywhichpkg markdown) \
$(pywhichpkg django) \
$(pywhichpkg yaml) \
-s '#! /usr/bin/env python2.7' \
-o relate-validate.pyz \
-x migrations \
-x templates \
-x 'static/' \
-x '\..*\.sw[op]' \
-x 'django/db' \
-x 'django/contrib' \
-x 'django/core/management' \
-x 'django/conf/locale' \
-x 'django/test' \
-x 'django/template' \
-x 'django/middleware' \
-x 'django/views' \
-x 'django/http' \
-x 'django/core/serial' \
-x 'django/core/mail' \
-x '_doctest' \
-x '.*~' \
-m course.validation:validate_course_on_filesystem_script_entrypoint