Commit 877f757f by jarv

Merge pull request #1369 from MITx/feature/jarv/certificate-restriction

Feature/jarv/certificate restriction
parents 424cfd3c 3f740976
from django.core.management.base import BaseCommand, CommandError
import os
from optparse import make_option
from student.models import UserProfile
import csv
class Command(BaseCommand):
help = """
Sets or gets certificate restrictions for users
from embargoed countries. (allow_certificate in
userprofile)
CSV should be comma delimited with double quoted entries.
$ ... cert_restriction --import path/to/userlist.csv
Export a list of students who have "allow_certificate" in
userprofile set to True
$ ... cert_restriction --output path/to/export.csv
Enable a single user so she is not on the restricted list
$ ... cert_restriction -e user
Disable a single user so she is on the restricted list
$ ... cert_restriction -d user
"""
option_list = BaseCommand.option_list + (
make_option('-i', '--import',
metavar='IMPORT_FILE',
dest='import',
default=False,
help='csv file to import, comma delimitted file with '
'double-quoted entries'),
make_option('-o', '--output',
metavar='EXPORT_FILE',
dest='output',
default=False,
help='csv file to export'),
make_option('-e', '--enable',
metavar='STUDENT',
dest='enable',
default=False,
help="enable a single student's certificate"),
make_option('-d', '--disable',
metavar='STUDENT',
dest='disable',
default=False,
help="disable a single student's certificate")
)
def handle(self, *args, **options):
if options['output']:
if os.path.exists(options['output']):
raise CommandError("File {0} already exists".format(
options['output']))
disabled_users = UserProfile.objects.filter(
allow_certificate=False)
with open(options['output'], 'w') as csvfile:
csvwriter = csv.writer(csvfile, delimiter=',', quotechar='"',
quoting=csv.QUOTE_MINIMAL)
for user in disabled_users:
csvwriter.writerow([user.user.username])
elif options['import']:
if not os.path.exists(options['import']):
raise CommandError("File {0} does not exist".format(
options['import']))
print "Importing students from {0}".format(options['import'])
students = None
with open(options['import']) as csvfile:
student_list = csv.reader(csvfile, delimiter=',',
quotechar='"')
students = [student[0] for student in student_list]
if not students:
raise CommandError(
"Unable to read student data from {0}".format(
options['import']))
UserProfile.objects.filter(user__username__in=students).update(
allow_certificate=False)
elif options['enable']:
print "Enabling {0} for certificate download".format(
options['enable'])
cert_allow = UserProfile.objects.get(
user__username=options['enable'])
cert_allow.allow_certificate = True
cert_allow.save()
elif options['disable']:
print "Disabling {0} for certificate download".format(
options['disable'])
cert_allow = UserProfile.objects.get(
user__username=options['disable'])
cert_allow.allow_certificate = False
cert_allow.save()
...@@ -90,6 +90,7 @@ class UserProfile(models.Model): ...@@ -90,6 +90,7 @@ class UserProfile(models.Model):
) )
mailing_address = models.TextField(blank=True, null=True) mailing_address = models.TextField(blank=True, null=True)
goals = models.TextField(blank=True, null=True) goals = models.TextField(blank=True, null=True)
allow_certificate = models.BooleanField(default=1)
def get_meta(self): def get_meta(self):
js_str = self.meta js_str = self.meta
...@@ -409,11 +410,11 @@ class TestCenterRegistration(models.Model): ...@@ -409,11 +410,11 @@ class TestCenterRegistration(models.Model):
# Someday this could go in the database (with a default value). But at present, # Someday this could go in the database (with a default value). But at present,
# we do not expect anyone to be authorized to take an exam more than once. # we do not expect anyone to be authorized to take an exam more than once.
return 1 return 1
@property @property
def needs_uploading(self): def needs_uploading(self):
return self.uploaded_at is None or self.uploaded_at < self.user_updated_at return self.uploaded_at is None or self.uploaded_at < self.user_updated_at
@classmethod @classmethod
def create(cls, testcenter_user, exam, accommodation_request): def create(cls, testcenter_user, exam, accommodation_request):
registration = cls(testcenter_user = testcenter_user) registration = cls(testcenter_user = testcenter_user)
...@@ -574,7 +575,7 @@ def get_testcenter_registration(user, course_id, exam_series_code): ...@@ -574,7 +575,7 @@ def get_testcenter_registration(user, course_id, exam_series_code):
# nosetests thinks that anything with _test_ in the name is a test. # nosetests thinks that anything with _test_ in the name is a test.
# Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html) # Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html)
get_testcenter_registration.__test__ = False get_testcenter_registration.__test__ = False
def unique_id_for_user(user): def unique_id_for_user(user):
""" """
Return a unique id for a user, suitable for inserting into Return a unique id for a user, suitable for inserting into
......
...@@ -135,7 +135,7 @@ def cert_info(user, course): ...@@ -135,7 +135,7 @@ def cert_info(user, course):
Get the certificate info needed to render the dashboard section for the given Get the certificate info needed to render the dashboard section for the given
student and course. Returns a dictionary with keys: student and course. Returns a dictionary with keys:
'status': one of 'generating', 'ready', 'notpassing', 'processing' 'status': one of 'generating', 'ready', 'notpassing', 'processing', 'restricted'
'show_download_url': bool 'show_download_url': bool
'download_url': url, only present if show_download_url is True 'download_url': url, only present if show_download_url is True
'show_disabled_download_button': bool -- true if state is 'generating' 'show_disabled_download_button': bool -- true if state is 'generating'
...@@ -168,6 +168,7 @@ def _cert_info(user, course, cert_status): ...@@ -168,6 +168,7 @@ def _cert_info(user, course, cert_status):
CertificateStatuses.regenerating: 'generating', CertificateStatuses.regenerating: 'generating',
CertificateStatuses.downloadable: 'ready', CertificateStatuses.downloadable: 'ready',
CertificateStatuses.notpassing: 'notpassing', CertificateStatuses.notpassing: 'notpassing',
CertificateStatuses.restricted: 'restricted',
} }
status = template_state.get(cert_status['status'], default_status) status = template_state.get(cert_status['status'], default_status)
...@@ -176,7 +177,7 @@ def _cert_info(user, course, cert_status): ...@@ -176,7 +177,7 @@ def _cert_info(user, course, cert_status):
'show_download_url': status == 'ready', 'show_download_url': status == 'ready',
'show_disabled_download_button': status == 'generating',} 'show_disabled_download_button': status == 'generating',}
if (status in ('generating', 'ready', 'notpassing') and if (status in ('generating', 'ready', 'notpassing', 'restricted') and
course.end_of_course_survey_url is not None): course.end_of_course_survey_url is not None):
d.update({ d.update({
'show_survey_button': True, 'show_survey_button': True,
...@@ -192,7 +193,7 @@ def _cert_info(user, course, cert_status): ...@@ -192,7 +193,7 @@ def _cert_info(user, course, cert_status):
else: else:
d['download_url'] = cert_status['download_url'] d['download_url'] = cert_status['download_url']
if status in ('generating', 'ready', 'notpassing'): if status in ('generating', 'ready', 'notpassing', 'restricted'):
if 'grade' not in cert_status: if 'grade' not in cert_status:
# Note: as of 11/20/2012, we know there are students in this state-- cs169.1x, # Note: as of 11/20/2012, we know there are students in this state-- cs169.1x,
# who need to be regraded (we weren't tracking 'notpassing' at first). # who need to be regraded (we weren't tracking 'notpassing' at first).
......
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
from certificates.models import CertificateWhitelist
from django.contrib.auth.models import User
class Command(BaseCommand):
help = """
Sets or gets the certificate whitelist for a given
user/course
Add a user to the whitelist for a course
$ ... cert_whitelist --add joe -c "MITx/6.002x/2012_Fall"
Remove a user from the whitelist for a course
$ ... cert_whitelist --del joe -c "MITx/6.002x/2012_Fall"
Print out who is whitelisted for a course
$ ... cert_whitelist -c "MITx/6.002x/2012_Fall"
"""
option_list = BaseCommand.option_list + (
make_option('-a', '--add',
metavar='USER',
dest='add',
default=False,
help='user to add to the certificate whitelist'),
make_option('-d', '--del',
metavar='USER',
dest='del',
default=False,
help='user to remove from the certificate whitelist'),
make_option('-c', '--course-id',
metavar='COURSE_ID',
dest='course_id',
default=False,
help="course id to query"),
)
def handle(self, *args, **options):
course_id = options['course_id']
if not course_id:
raise CommandError("You must specify a course-id")
if options['add'] and options['del']:
raise CommandError("Either remove or add a user, not both")
if options['add'] or options['del']:
user_str = options['add'] or options['del']
if '@' in user_str:
user = User.objects.get(email=user_str)
else:
user = User.objects.get(username=user_str)
cert_whitelist, created = \
CertificateWhitelist.objects.get_or_create(
user=user, course_id=course_id)
if options['add']:
cert_whitelist.whitelist = True
elif options['del']:
cert_whitelist.whitelist = False
cert_whitelist.save()
whitelist = CertificateWhitelist.objects.all()
print "User whitelist for course {0}:\n{1}".format(course_id,
'\n'.join(["{0} {1} {2}".format(
u.user.username, u.user.email, u.whitelist)
for u in whitelist]))
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'CertificateWhitelist'
db.create_table('certificates_certificatewhitelist', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('course_id', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True)),
('whitelist', self.gf('django.db.models.fields.BooleanField')(default=False)),
))
db.send_create_signal('certificates', ['CertificateWhitelist'])
def backwards(self, orm):
# Deleting model 'CertificateWhitelist'
db.delete_table('certificates_certificatewhitelist')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'certificates.certificatewhitelist': {
'Meta': {'object_name': 'CertificateWhitelist'},
'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'whitelist': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'certificates.generatedcertificate': {
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'},
'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
'created_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
'distinction': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'download_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
'download_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'error_reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}),
'grade': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '5', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'unavailable'", 'max_length': '32'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'verify_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['certificates']
\ No newline at end of file
...@@ -35,6 +35,19 @@ State diagram: ...@@ -35,6 +35,19 @@ State diagram:
v v v v v v
[downloadable] [downloadable] [deleted] [downloadable] [downloadable] [deleted]
Eligibility:
Students are eligible for a certificate if they pass the course
with the following exceptions:
If the student has allow_certificate set to False in the student profile
he will never be issued a certificate.
If the user and course is present in the certificate whitelist table
then the student will be issued a certificate regardless of his grade,
unless he has allow_certificate set to False.
""" """
...@@ -46,8 +59,20 @@ class CertificateStatuses(object): ...@@ -46,8 +59,20 @@ class CertificateStatuses(object):
deleted = 'deleted' deleted = 'deleted'
downloadable = 'downloadable' downloadable = 'downloadable'
notpassing = 'notpassing' notpassing = 'notpassing'
restricted = 'restricted'
error = 'error' error = 'error'
class CertificateWhitelist(models.Model):
"""
Tracks students who are whitelisted, all users
in this table will always qualify for a certificate
regardless of their grade unless they are on the
embargoed country restriction list
(allow_certificate set to False in userprofile).
"""
user = models.ForeignKey(User)
course_id = models.CharField(max_length=255, blank=True, default='')
whitelist = models.BooleanField(default=0)
class GeneratedCertificate(models.Model): class GeneratedCertificate(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User)
...@@ -87,6 +112,10 @@ def certificate_status_for_student(student, course_id): ...@@ -87,6 +112,10 @@ def certificate_status_for_student(student, course_id):
deleted - The certificate has been deleted. deleted - The certificate has been deleted.
downloadable - The certificate is available for download. downloadable - The certificate is available for download.
notpassing - The student was graded but is not passing notpassing - The student was graded but is not passing
restricted - The student is on the restricted embargo list and
should not be issued a certificate. This will
be set if allow_certificate is set to False in
the userprofile table
If the status is "downloadable", the dictionary also contains If the status is "downloadable", the dictionary also contains
"download_url". "download_url".
......
from certificates.models import GeneratedCertificate from certificates.models import GeneratedCertificate
from certificates.models import certificate_status_for_student from certificates.models import certificate_status_for_student
from certificates.models import CertificateStatuses as status from certificates.models import CertificateStatuses as status
from certificates.models import CertificateWhitelist
from courseware import grades, courses from courseware import grades, courses
from django.test.client import RequestFactory from django.test.client import RequestFactory
...@@ -71,6 +72,8 @@ class XQueueCertInterface(object): ...@@ -71,6 +72,8 @@ class XQueueCertInterface(object):
settings.XQUEUE_INTERFACE['django_auth'], settings.XQUEUE_INTERFACE['django_auth'],
requests_auth, requests_auth,
) )
self.whitelist = CertificateWhitelist.objects.all()
self.restricted = UserProfile.objects.filter(allow_certificate=False)
def regen_cert(self, student, course_id): def regen_cert(self, student, course_id):
""" """
...@@ -93,49 +96,7 @@ class XQueueCertInterface(object): ...@@ -93,49 +96,7 @@ class XQueueCertInterface(object):
""" """
VALID_STATUSES = [status.error, status.downloadable] raise NotImplementedError
cert_status = certificate_status_for_student(
student, course_id)['status']
if cert_status in VALID_STATUSES:
# grade the student
course = courses.get_course_by_id(course_id)
grade = grades.grade(student, self.request, course)
profile = UserProfile.objects.get(user=student)
try:
cert = GeneratedCertificate.objects.get(
user=student, course_id=course_id)
except GeneratedCertificate.DoesNotExist:
logger.critical("Attempting to regenerate a certificate"
"for a user that doesn't have one")
raise
if grade['grade'] is not None:
cert.status = status.regenerating
cert.name = profile.name
contents = {
'action': 'regen',
'delete_verify_uuid': cert.verify_uuid,
'delete_download_uuid': cert.download_uuid,
'username': cert.user.username,
'course_id': cert.course_id,
'name': profile.name,
}
key = cert.key
self._send_to_xqueue(contents, key)
cert.save()
else:
cert.status = status.notpassing
cert.name = profile.name
cert.save()
return cert_status
def del_cert(self, student, course_id): def del_cert(self, student, course_id):
...@@ -152,34 +113,7 @@ class XQueueCertInterface(object): ...@@ -152,34 +113,7 @@ class XQueueCertInterface(object):
""" """
VALID_STATUSES = [status.error, status.downloadable] raise NotImplementedError
cert_status = certificate_status_for_student(
student, course_id)['status']
if cert_status in VALID_STATUSES:
try:
cert = GeneratedCertificate.objects.get(
user=student, course_id=course_id)
except GeneratedCertificate.DoesNotExist:
logger.warning("Attempting to delete a certificate"
"for a user that doesn't have one")
raise
cert.status = status.deleting
contents = {
'action': 'delete',
'delete_verify_uuid': cert.verify_uuid,
'delete_download_uuid': cert.download_uuid,
'username': cert.user.username,
}
key = cert.key
self._send_to_xqueue(contents, key)
cert.save()
return cert_status
def add_cert(self, student, course_id): def add_cert(self, student, course_id):
""" """
...@@ -189,13 +123,17 @@ class XQueueCertInterface(object): ...@@ -189,13 +123,17 @@ class XQueueCertInterface(object):
course_id - courseenrollment.course_id (string) course_id - courseenrollment.course_id (string)
Request a new certificate for a student. Request a new certificate for a student.
Will change the certificate status to 'deleting'. Will change the certificate status to 'generating'.
Certificate must be in the 'unavailable', 'error', Certificate must be in the 'unavailable', 'error',
'deleted' or 'generating' state. 'deleted' or 'generating' state.
If a student has a passing grade a request will made If a student has a passing grade or is in the whitelist
for a new cert table for the course a request will made for a new cert.
If a student has allow_certificate set to False in the
userprofile table the status will change to 'restricted'
If a student does not have a passing grade the status If a student does not have a passing grade the status
will change to status.notpassing will change to status.notpassing
...@@ -214,30 +152,41 @@ class XQueueCertInterface(object): ...@@ -214,30 +152,41 @@ class XQueueCertInterface(object):
if cert_status in VALID_STATUSES: if cert_status in VALID_STATUSES:
# grade the student # grade the student
course = courses.get_course_by_id(course_id) course = courses.get_course_by_id(course_id)
grade = grades.grade(student, self.request, course)
profile = UserProfile.objects.get(user=student) profile = UserProfile.objects.get(user=student)
cert, created = GeneratedCertificate.objects.get_or_create( cert, created = GeneratedCertificate.objects.get_or_create(
user=student, course_id=course_id) user=student, course_id=course_id)
if grade['grade'] is not None: grade = grades.grade(student, self.request, course)
cert_status = status.generating is_whitelisted = self.whitelist.filter(
user=student, course_id=course_id, whitelist=True).exists()
if is_whitelisted or grade['grade'] is not None:
key = make_hashkey(random.random()) key = make_hashkey(random.random())
cert.status = cert_status
cert.grade = grade['percent'] cert.grade = grade['percent']
cert.user = student cert.user = student
cert.course_id = course_id cert.course_id = course_id
cert.key = key cert.key = key
cert.name = profile.name cert.name = profile.name
contents = { # check to see whether the student is on the
'action': 'create', # the embargoed country restricted list
'username': student.username, # otherwise, put a new certificate request
'course_id': course_id, # on the queue
'name': profile.name, if self.restricted.filter(user=student).exists():
} cert.status = status.restricted
else:
self._send_to_xqueue(contents, key) contents = {
'action': 'create',
'username': student.username,
'course_id': course_id,
'name': profile.name,
}
cert.status = status.generating
self._send_to_xqueue(contents, key)
cert.save() cert.save()
else: else:
cert_status = status.notpassing cert_status = status.notpassing
......
...@@ -273,12 +273,16 @@ ...@@ -273,12 +273,16 @@
% if cert_status['status'] == 'processing': % if cert_status['status'] == 'processing':
<p class="message-copy">Final course details are being wrapped up at <p class="message-copy">Final course details are being wrapped up at
this time. Your final standing will be available shortly.</p> this time. Your final standing will be available shortly.</p>
% elif cert_status['status'] in ('generating', 'ready', 'notpassing'): % elif cert_status['status'] in ('generating', 'ready', 'notpassing', 'restricted'):
<p class="message-copy">Your final grade: <p class="message-copy">Your final grade:
<span class="grade-value">${"{0:.0f}%".format(float(cert_status['grade'])*100)}</span>. <span class="grade-value">${"{0:.0f}%".format(float(cert_status['grade'])*100)}</span>.
% if cert_status['status'] == 'notpassing': % if cert_status['status'] == 'notpassing':
Grade required for a certificate: <span class="grade-value"> Grade required for a certificate: <span class="grade-value">
${"{0:.0f}%".format(float(course.lowest_passing_grade)*100)}</span>. ${"{0:.0f}%".format(float(course.lowest_passing_grade)*100)}</span>.
% elif cert_status['status'] == 'restricted':
<p class="message-copy">
Your certificate is being held while we seek confirmation that the issuance of your certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting <a class="contact-link" href="mailto:info@edx.org">info@edx.org</a>.
</p>
% endif % endif
</p> </p>
% endif % endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment