Merge branch 'feature/childs-11-06-19' into 'master'

Feature/childs 11 06 19

See merge request lilschool/site!321
remotes/origin/feature/drawing-camp
Danil 7 years ago
commit 9ff0eda77d
  1. 80
      apps/notification/tasks.py
  2. 4
      apps/notification/templates/notification/email/camp_certificate.html
  3. 25
      apps/notification/templates/notification/email/certificate.html
  4. 20
      apps/user/forms.py
  5. 26
      apps/user/migrations/0032_child.py
  6. 21
      apps/user/migrations/0033_add_childs.py
  7. 29
      apps/user/migrations/0034_auto_20190612_1852.py
  8. 34
      apps/user/models.py
  9. 2
      apps/user/templates/user/bonus-history.html
  10. 99
      apps/user/templates/user/profile-settings.html
  11. 2
      apps/user/templates/user/profile.html
  12. 74
      apps/user/views.py
  13. 3
      project/tests/test_features.py
  14. 119
      web/src/components/Childs.vue
  15. 4
      web/src/js/pages/profile.js
  16. 12
      web/src/sass/_common.sass

@ -3,12 +3,14 @@ from datetime import datetime, date, timedelta
from PIL import Image from PIL import Image
from PIL import ImageFont from PIL import ImageFont
from PIL import ImageDraw from PIL import ImageDraw
from unidecode import unidecode
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings from django.conf import settings
from django.utils.text import slugify
from apps.notification.models import UserNotification from apps.notification.models import UserNotification
from apps.notification.utils import send_email from apps.notification.utils import send_email
@ -16,6 +18,7 @@ from apps.payment.models import SchoolPayment, CoursePayment, Payment, UserGiftC
from project.celery import app from project.celery import app
from project.utils.db import format_sql, execute_sql from project.utils.db import format_sql, execute_sql
from project.sengrid import get_sendgrid_client from project.sengrid import get_sendgrid_client
from apps.user.models import Child
User = get_user_model() User = get_user_model()
@ -36,7 +39,11 @@ def draw_cert(path, email, first_name, last_name, x=None, y=900, color=(29, 115,
os.mkdir(fn) os.mkdir(fn)
except: except:
pass pass
fn = os.path.join(fn, '%scertificate-for-%s.jpg' % (fn_prefix, email)) if first_name:
name = '-'.join(filter(None, [first_name, last_name]))
fn = os.path.join(fn, '%scertificate-for-%s-%s.jpg' % (fn_prefix, email, slugify(unidecode(name))))
else:
fn = os.path.join(fn, '%scertificate-for-%s.jpg' % (fn_prefix, email))
img.save(fn) img.save(fn)
img.close() img.close()
return fn return fn
@ -76,25 +83,30 @@ def send_certificates(email=None, date_end=None, dry_run=False):
if dry_run: if dry_run:
continue continue
file_names = []
un.certificate_number = un.certificate_number + 1 \ un.certificate_number = un.certificate_number + 1 \
if un.certificate_number and staticfiles_storage.exists(path_pattern % (un.certificate_number + 1)) \ if un.certificate_number and staticfiles_storage.exists(path_pattern % (un.certificate_number + 1)) \
else 1 else 1
if un.user.child_first_name: if un.user.child_filled:
fn = staticfiles_storage.path(signed_path_pattern % un.certificate_number) fn = staticfiles_storage.path(signed_path_pattern % un.certificate_number)
fn = draw_cert(fn, un.user.email, un.user.child_first_name, un.user.child_last_name) for child in un.user.childs.all():
file_names.append(draw_cert(fn, un.user.email, child.first_name, child.last_name))
else: else:
fn = staticfiles_storage.path(path_pattern % un.certificate_number) file_names.append(staticfiles_storage.path(path_pattern % un.certificate_number))
file = open(fn, 'rb') files = [open(fn, 'rb') for fn in file_names]
try: try:
send_email('Грамота от Lil School', un.user.email, 'notification/email/certificate.html', send_email('Грамота от Lil School', un.user.email, 'notification/email/certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')], user_notification=un) attachments=[(f.name, f.read(), 'image/jpeg') for f in files], user_notification=un,
many_childs=un.user.childs.all().count() > 1)
except: except:
print('Not OK') print('Not OK')
continue continue
finally: finally:
file.close() for f in files:
if un.user.child_first_name: f.close()
os.remove(fn) if un.user.child_filled:
for fn in file_names:
os.remove(fn)
un.certificate_last_email = now() un.certificate_last_email = now()
un.save() un.save()
@ -148,19 +160,20 @@ def send_gift_certificate(user_gift_certificate):
@app.task @app.task
def send_child_birthday_email_and_bonuses(): def send_child_birthday_email_and_bonuses():
for u in User.objects.exclude(child_first_name='', child_last_name='',).filter(child_birthday=now().date()): for user_id, email in set(Child.objects.filter(birthday=now().date()).select_related('user')
if not UserBonus.objects.filter(user=u, is_service=True, action_name=UserBonus.ACTION_CHILD_BIRTHDAY).count(): .values_list('user_id', 'user__email')):
print('user', u.email) print('user', email)
UserBonus.objects.create(user=u, amount=UserBonus.AMOUNT_CHILD_BIRTHDAY, is_service=True, if not UserBonus.objects.filter(user=user_id, is_service=True, action_name=UserBonus.ACTION_CHILD_BIRTHDAY).count():
UserBonus.objects.create(user=user_id, amount=UserBonus.AMOUNT_CHILD_BIRTHDAY, is_service=True,
action_name=UserBonus.ACTION_CHILD_BIRTHDAY) action_name=UserBonus.ACTION_CHILD_BIRTHDAY)
try: try:
fn = staticfiles_storage.path('img/birthday_postcard.jpg') fn = staticfiles_storage.path('img/birthday_postcard.jpg')
file = open(fn, 'rb') file = open(fn, 'rb')
send_email('С Днем Рождения!', u.email, 'notification/email/birthday_postcard.html', send_email('С Днем Рождения!', email, 'notification/email/birthday_postcard.html',
attachments=[(file.name, file.read(), 'image/jpeg')],) attachments=[(file.name, file.read(), 'image/jpeg')],)
print('email has been sent') print('email has been sent')
finally: finally:
file.close() file.close()
@app.task @app.task
def send_camp_certificates(email=None, dry_run=False, certificate_number=None): def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
@ -178,7 +191,6 @@ def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
font_size=120, y=1000, color=color) font_size=120, y=1000, color=color)
file = open(fn, 'rb') file = open(fn, 'rb')
try: try:
pass
send_email('Грамота от Lil School', email, 'notification/email/camp_certificate.html', send_email('Грамота от Lil School', email, 'notification/email/camp_certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')], certificate_number=certificate_number) attachments=[(file.name, file.read(), 'image/jpeg')], certificate_number=certificate_number)
except: except:
@ -205,23 +217,27 @@ def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
if dry_run: if dry_run:
continue continue
if un.user.child_first_name: file_names = []
if un.user.child_filled:
fn = staticfiles_storage.path(signed_path_pattern % certificate_number) fn = staticfiles_storage.path(signed_path_pattern % certificate_number)
fn = draw_cert(fn, un.user.email, un.user.child_first_name, un.user.child_last_name, for child in un.user.childs.all():
font_size=120, y=1000, color=color) file_names.append(draw_cert(fn, un.user.email, child.first_name, child.last_name,
font_size=120, y=1000, color=color))
else: else:
fn = staticfiles_storage.path(path_pattern % certificate_number) file_names.append(staticfiles_storage.path(path_pattern % certificate_number))
file = open(fn, 'rb') files = [open(fn, 'rb') for fn in file_names]
try: try:
send_email('Грамота от Lil School', un.user.email, 'notification/email/camp_certificate.html', send_email('Грамота от Lil School', un.user.email, 'notification/email/camp_certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')], user_notification=un, attachments=[(f.name, f.read(), 'image/jpeg') for f in files], user_notification=un,
certificate_number=certificate_number) certificate_number=certificate_number, many_childs=un.user.childs.all().count() > 1)
except: except:
print('Not OK') print('Not OK')
continue continue
finally: finally:
file.close() for f in files:
if un.user.child_first_name: f.close()
os.remove(fn) if un.user.child_filled:
for fn in file_names:
os.remove(fn)
un.camp_certificate_last_email = date_end un.camp_certificate_last_email = date_end
un.save() un.save()

@ -6,13 +6,13 @@
{% if certificate_number == 1 %} {% if certificate_number == 1 %}
<p> <p>
Поздравляем! Вы прошли месяц обучения в лагере Lil School.<br> Поздравляем! Вы прошли месяц обучения в лагере Lil School.<br>
К письму прикреплена грамота. Распечатайте её и вручите вашим детям.<br> К письму {% if many_childs %}прикреплены грамоты. Распечатайте их{% else %}прикреплена грамота. Распечатайте её{% endif %} и вручите вашим детям.<br>
Ждём вас в следующем месяце на наших творческих занятиях! Ждём вас в следующем месяце на наших творческих занятиях!
</p> </p>
{% endif %} {% endif %}
{% if certificate_number == 2 %} {% if certificate_number == 2 %}
<p> <p>
Вы помните, что каждый месяц вам приходит грамота за прекрасную учебу в рсиовальном лагере?<br> Вы помните, что каждый месяц вам {% if many_childs %}приходят грамоты{% else %}приходит грамота{% endif %} за прекрасную учебу в рисовальном лагере?<br>
Скачивайте. Распечатывайте. И соберите свою коллекцию! Скачивайте. Распечатывайте. И соберите свою коллекцию!
</p> </p>
{% endif %} {% endif %}

@ -6,20 +6,20 @@
{% if not user_notification or user_notification.certificate_number == 1 %} {% if not user_notification or user_notification.certificate_number == 1 %}
<p> <p>
Поздравляем! Вы прошли месяц обучения в Lil School.<br> Поздравляем! Вы прошли месяц обучения в Lil School.<br>
К письму прикреплена грамота. Распечатайте её и вручите вашим детям.<br> К письму {% if many_childs %}прикреплены грамоты. Распечатайте их{% else %}прикреплена грамота. Распечатайте её{% endif %} и вручите вашим детям.<br>
Ждём вас в следующем месяце на наших творческих занятиях! Ждём вас в следующем месяце на наших творческих занятиях!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 2 %} {% if user_notification and user_notification.certificate_number == 2 %}
<p> <p>
Вы помните, что каждый месяц вам приходит грамота за прекрасную учебу в нашей творческой школе?<br> Вы помните, что каждый месяц вам {% if many_childs %}приходят грамоты{% else %}приходит грамота{% endif %} за прекрасную учебу в нашей творческой школе?<br>
Скачивайте. Распечатывайте. И соберите свою коллекцию! Скачивайте. Распечатывайте. И соберите свою коллекцию!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 3 %} {% if user_notification and user_notification.certificate_number == 3 %}
<p> <p>
Вам понравился наш творческий месяц?<br> Вам понравился наш творческий месяц?<br>
В письме вы найдёте грамоту, она для вашей семьи.<br> В письме вы найдёте {% if many_childs %}грамоты, они{% else %}грамоту, она{% endif %} для вашей семьи.<br>
Как здорово, что у нас есть такие ученики!<br> Как здорово, что у нас есть такие ученики!<br>
Ждём вас в следующем месяце. Ждём вас в следующем месяце.
</p> </p>
@ -27,22 +27,27 @@
{% if user_notification and user_notification.certificate_number == 4 %} {% if user_notification and user_notification.certificate_number == 4 %}
<p> <p>
Прошёл целый месяц обучения на платформе Lil School - месяц творчества, креатива и невероятных идей.<br> Прошёл целый месяц обучения на платформе Lil School - месяц творчества, креатива и невероятных идей.<br>
Во вложении вас ждёт грамота.<br> Во вложении вас {% if many_childs %}ждут грамоты{% else %}ждёт грамота{% endif %}.<br>
До встречи на занятиях! До встречи на занятиях!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 5 %} {% if user_notification and user_notification.certificate_number == 5 %}
<p> <p>
{% if many_childs %}
Ваши грамоты за успехи в учебе в Lil School ждут вас во вложении.<br>
Скорее распечатайте их!<br>
{% else %}
Ваша грамота за успехи в учебе в Lil School ждёт вас во вложении.<br> Ваша грамота за успехи в учебе в Lil School ждёт вас во вложении.<br>
Скорее распечатайте её!<br> Скорее распечатайте её!<br>
Вам есть чем гордится!<br> {% endif %}
До встречи в следующем месяце! Вам есть чем гордится!<br>
До встречи в следующем месяце!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 6 %} {% if user_notification and user_notification.certificate_number == 6 %}
<p> <p>
Как здорово вы потрудились на занятиях в этом месяце!<br> Как здорово вы потрудились на занятиях в этом месяце!<br>
И наша грамота уже ждёт вас!<br> И {% if many_childs %}наши грамоты уже ждут{% else %}наша грамота уже ждёт{% endif %} вас!<br>
Спасибо за творчество и креатив.<br> Спасибо за творчество и креатив.<br>
Ждём вас в следующем месяце! Ждём вас в следующем месяце!
</p> </p>
@ -50,7 +55,7 @@
{% if user_notification and user_notification.certificate_number == 7 %} {% if user_notification and user_notification.certificate_number == 7 %}
<p> <p>
Какой классный месяц у нас был! Вместе мы очень здорово и креативно потрудились.<br> Какой классный месяц у нас был! Вместе мы очень здорово и креативно потрудились.<br>
Во вложении вас ждёт заслуженная грамота!<br> Во вложении вас {% if many_childs %}ждут заслуженные грамоты{% else %}ждёт заслуженная грамота{% endif %}!<br>
До встречи на уроках! До встречи на уроках!
</p> </p>
{% endif %} {% endif %}
@ -58,7 +63,7 @@
<p> <p>
Месяц творчества и креатива пролетел как один миг! А как много работ мы вместе сделали!<br> Месяц творчества и креатива пролетел как один миг! А как много работ мы вместе сделали!<br>
Вы - большие молодцы.<br> Вы - большие молодцы.<br>
Во вложении ваш ждёт грамота!<br> Во вложении вас {% if many_childs %}ждут грамоты{% else %}ждёт грамота{% endif %}!<br>
До встречи на занятиях. До встречи на занятиях.
</p> </p>
{% endif %} {% endif %}

@ -2,7 +2,8 @@ from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from .fields import CreditCardField from apps.user.fields import CreditCardField
from apps.user.models import Child
User = get_user_model() User = get_user_model()
@ -31,11 +32,6 @@ class UserEditForm(forms.ModelForm):
site = forms.URLField(required=False) site = forms.URLField(required=False)
photo = forms.ImageField(required=False) photo = forms.ImageField(required=False)
child_gender = forms.CharField(required=False)
child_birthday = forms.DateField(input_formats=['%d/%m/%Y'], required=False)
child_first_name = forms.CharField(required=False)
child_last_name = forms.CharField(required=False)
class Meta: class Meta:
model = User model = User
fields = ( fields = (
@ -61,10 +57,6 @@ class UserEditForm(forms.ModelForm):
'vkontakte', 'vkontakte',
'site', 'site',
'photo', 'photo',
'child_gender',
'child_birthday',
'child_first_name',
'child_last_name',
) )
@ -79,3 +71,11 @@ class AuthorRequesForm(forms.Form):
email = forms.CharField() email = forms.CharField()
about = forms.CharField() about = forms.CharField()
facebook = forms.URLField(required=False) facebook = forms.URLField(required=False)
class ChildForm(forms.ModelForm):
birthday = forms.DateField(input_formats=['%d/%m/%Y'], required=False)
class Meta:
model = Child
fields = '__all__'

@ -0,0 +1,26 @@
# Generated by Django 2.0.7 on 2019-06-06 21:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('user', '0031_user_review_url'),
]
operations = [
migrations.CreateModel(
name='Child',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=30, verbose_name='Имя ребенка')),
('last_name', models.CharField(blank=True, default='', max_length=150, verbose_name='Фамилия ребенка')),
('gender', models.CharField(choices=[('n', 'не указан'), ('m', 'Мужчина'), ('f', 'Женщина')], default='n', max_length=1, verbose_name='Пол ребенка')),
('birthday', models.DateField(blank=True, null=True, verbose_name='День рождения ребенка')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='childs', to=settings.AUTH_USER_MODEL)),
],
),
]

@ -0,0 +1,21 @@
# Generated by Django 2.0.7 on 2019-06-07 20:48
from django.db import migrations
def add_childs(apps, schema_editor):
User = apps.get_model('user', 'User')
Child = apps.get_model('user', 'Child')
for user in User.objects.exclude(child_first_name=''):
Child.objects.get_or_create(user=user, first_name=user.child_first_name, last_name=user.child_last_name,
gender=user.child_gender, birthday=user.child_birthday)
class Migration(migrations.Migration):
dependencies = [
('user', '0032_child'),
]
operations = [
migrations.RunPython(add_childs),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.7 on 2019-06-12 18:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0033_add_childs'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='child_birthday',
),
migrations.RemoveField(
model_name='user',
name='child_first_name',
),
migrations.RemoveField(
model_name='user',
name='child_gender',
),
migrations.RemoveField(
model_name='user',
name='child_last_name',
),
]

@ -88,12 +88,6 @@ class User(AbstractUser):
'content.Gallery', on_delete=models.CASCADE, 'content.Gallery', on_delete=models.CASCADE,
verbose_name='Галерея', null=True, blank=True, verbose_name='Галерея', null=True, blank=True,
) )
child_first_name = models.CharField('Имя ребенка', max_length=30, blank=True)
child_last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True)
child_gender = models.CharField(
'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES)
child_birthday = models.DateField('День рождения ребенка', null=True, blank=True)
review_url = models.URLField('Ссылка на видеоотзыв', blank=True, default='') review_url = models.URLField('Ссылка на видеоотзыв', blank=True, default='')
objects = UserManager() objects = UserManager()
@ -104,6 +98,14 @@ class User(AbstractUser):
class Meta(AbstractUser.Meta): class Meta(AbstractUser.Meta):
ordering = ('-date_joined',) ordering = ('-date_joined',)
@cached_property
def has_child(self):
return self.childs.all().count() > 0
def child_filled(self):
child = self.childs.all().first()
return child and child.first_name and child.last_name and child.birthday
@property @property
def url(self): def url(self):
return reverse('user', args=[self.slug or self.id]) return reverse('user', args=[self.slug or self.id])
@ -338,3 +340,23 @@ class EmailLog(models.Model):
email = models.EmailField(_('email address')) email = models.EmailField(_('email address'))
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES) source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)
class Child(models.Model):
NOT_DEFINED = 'n'
MALE = 'm'
FEMALE = 'f'
GENDER_CHOICES = (
(NOT_DEFINED, 'не указан'),
(MALE, 'Мужчина'),
(FEMALE, 'Женщина'),
)
user = models.ForeignKey(User, related_name='childs', on_delete=models.CASCADE)
first_name = models.CharField('Имя ребенка', max_length=30)
last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True, default='')
gender = models.CharField(
'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES)
birthday = models.DateField('День рождения ребенка', null=True, blank=True)
class Meta:
ordering = ('id',)

@ -83,7 +83,7 @@
</div> </div>
<div>+{{ amount_child_birthday }} LIL</div> <div>+{{ amount_child_birthday }} LIL</div>
<div><a class="btn btn_stroke" href="{% url 'user-profile' %}">ЗАПОЛНИТЬ КАРТОЧКУ РЕБЕНКА</a></div> <div><a class="btn btn_stroke" href="{% url 'user-profile' %}">ЗАПОЛНИТЬ КАРТОЧКУ РЕБЕНКА</a></div>
<div>{% if request.user.child_first_name and request.user.child_last_name and request.user.child_birthday %} <div>{% if request.user.child_filled %}
<img src="{% static 'img/done2.png' %}" />{% endif %}</div> <img src="{% static 'img/done2.png' %}" />{% endif %}</div>
</div> </div>
</div> </div>

@ -67,101 +67,41 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="form__field field{% if form.email.errors %} error{% endif %}">
<div class="field__label">Почта</div>
<div class="field__wrap">
<input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
</div>
{% for error in form.email.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">Ссылка</div>
<div class="field__wrap field__wrap__appended">
<div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div>
{% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<!-- child form -->
<div class="user-child-form form__group">
<div class="form__title">Карточка ребёнка</div>
<div class="user-child-form__description">
Вся информация конфиденциальна и не передается третьим лицам. Необходима только для персонализации наград,
поздравлений с Днем Рождения и других персонализированных акций.
</div>
<div class="form__fieldset">
<div class="form__field field{% if form.child_first_name.errors %} error{% endif %}">
<div class="field__label">ИМЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_first_name' id="child-first-name" class="field__input" type="text" placeholder="Имя"
value="{{ user.child_first_name }}">
</div>
{% for error in form.child_first_name.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.child_last_name.errors %} error{% endif %}">
<div class="field__label">ФАМИЛИЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_last_name' id="child-last-name" class="field__input" type="text" placeholder="Фамилия"
value="{{ user.child_last_name }}">
</div>
{% for error in form.child_last_name.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<div class="form__fieldset"> <div class="form__fieldset">
<div class="form__field field{% if form.child_birthday.errors %} error{% endif %}"> <div class="form__field field{% if form.email.errors %} error{% endif %}">
<div class="field__label">ДАТА РОЖДЕНИЯ</div> <div class="field__label">Почта</div>
<div class="field__wrap"> <div class="field__wrap">
<vue-datepicker input-class="field__input" name="child_birthday" language="ru" format="dd/MM/yyyy" <input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
value="{% if user.child_birthday %}{{ user.child_birthday | date:'Y-m-d' }}{% endif %}" placeholder="dd/mm/yyyy"/>
</div> </div>
{% for error in form.child_birthday.errors %} {% for error in form.email.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="form__field field{% if form.child_gender.errors %} error{% endif %}"> <div class="form__field field{% if form.phone.errors %} error{% endif %}">
<div class="field__label">ПОЛ</div> <div class="field__label">НОМЕР ТЕЛЕФОНА</div>
<div class="field__wrap"> <div class="field__wrap">
<div class="field__select select js-select{% if user.child_gender and user.child_gender != 'n' %} selected{% endif %}"> <input name='phone' class="field__input" type="phone" placeholder="+7 (999) 999-99-99" value="{{ user.phone|default:'' }}">
<div class="select__head js-select-head">
{% if user.child_gender == 'f' %}Ж{% elif user.child_gender == 'm' %}M{% else %}М / Ж{% endif %}
</div>
<div class="select__drop js-select-drop" data-gender-select data-input="#child-gender">
<div class="select__option js-select-option" data-gender-option data-gender="m">
<div class="select__title">М</div>
</div>
<div class="select__option js-select-option" data-gender-option data-gender="f">
<div class="select__title">Ж</div>
</div>
</div>
<input id="child-gender" name='child_gender' class="select__input" type="hidden">
</div>
</div> </div>
{% for error in form.child_gender.errors %} {% for error in form.phone.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="form__field field{% if form.phone.errors %} error{% endif %}"> <div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">НОМЕР ТЕЛЕФОНА РОДИТЕЛЯ</div> <div class="field__label">Ссылка</div>
<div class="field__wrap"> <div class="field__wrap field__wrap__appended">
<input name='phone' class="field__input" type="phone" placeholder="+7 (999) 999-99-99" value="{{ user.phone|default:'' }}"> <div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div> </div>
{% for error in form.phone.errors %} {% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<!-- end child form -->
<childs :childs="$root.store.data.childs"></childs>
<div class="form__group"> <div class="form__group">
<div class="form__fieldset"> <div class="form__fieldset">
<div class="form__field field{% if form.city.errors %} error{% endif %}"> <div class="form__field field{% if form.city.errors %} error{% endif %}">
@ -393,5 +333,8 @@
{% endblock foot %} {% endblock foot %}
{% block pre_app_js %} {% block pre_app_js %}
<script>
window.LIL_STORE.data.childs = {{ childs|safe }};
</script>
<script type="text/javascript" src="{% static 'profile.js' %}"></script> <script type="text/javascript" src="{% static 'profile.js' %}"></script>
{% endblock pre_app_js %} {% endblock pre_app_js %}

@ -1,6 +1,6 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %} {% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %}
{% if not user.child_first_name or not user.child_birthday %} {% if not user.has_child %}
<div class="section"> <div class="section">
<div class="section__center center"> <div class="section__center center">
<div class="fill-profile"> <div class="fill-profile">

@ -26,9 +26,9 @@ from apps.notification.utils import send_email
from apps.school.models import SchoolSchedule from apps.school.models import SchoolSchedule
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate, UserBonus, \ from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate, UserBonus, \
DrawingCampPayment DrawingCampPayment
from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory, Child
from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm, ChildForm
User = get_user_model() User = get_user_model()
@ -217,6 +217,26 @@ class ProfileEditView(UpdateView):
context['is_teacher'] = self.object.role == User.TEACHER_ROLE context['is_teacher'] = self.object.role == User.TEACHER_ROLE
return context return context
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if self.object.childs.all().count():
context['childs'] = [{
'id': c.id,
'first_name': c.first_name,
'last_name': c.last_name,
'gender': c.gender or 'n',
'birthday': c.birthday.strftime('%Y-%m-%d') if c.birthday else '',
} for c in self.object.childs.all()]
else:
context['childs'] = [{
'id': '',
'first_name': '',
'last_name': '',
'gender': 'n',
'birthday': '',
}]
return self.render_to_response(context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-* # it's magic *-*-*-*-*
if 'photo' in request.FILES: if 'photo' in request.FILES:
@ -256,15 +276,47 @@ class ProfileEditView(UpdateView):
login(request, request.user) login(request, request.user)
else: else:
messages.error(request, 'Неверный пароль.') messages.error(request, 'Неверный пароль.')
messages.info(request, 'Данные сохранены.')
response = super().post(request, *args, **kwargs) child_ids = request.POST.getlist('child_id')
user = User.objects.get(pk=request.user.id) child_first_names = request.POST.getlist('child_first_name')
# начисляемм бонусы, если заполнил профиль child_last_names = request.POST.getlist('child_last_name')
if user.phone and user.first_name and user.last_name and not user.bonuses.filter( child_genders = request.POST.getlist('child_gender')
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count(): child_birthdays = request.POST.getlist('child_birthday')
UserBonus.objects.create(user=user, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True, ziped = zip(child_ids, child_first_names, child_last_names, child_genders, child_birthdays)
action_name=UserBonus.ACTION_FILL_PROFILE) childs = [dict(zip(['id', 'first_name', 'last_name', 'gender', 'birthday'], z)) for z in ziped]
return response childs_saved = []
child_errors = False
for child in childs:
child_instance = None
if child.get('id'):
child_instance = Child.objects.get(pk=child.get('id'))
child['user'] = request.user.id
child['gender'] = child['gender'] or 'n'
child_form = ChildForm(data=child, instance=child_instance)
if child_form.is_valid():
childs_saved.append(child_form.save().id)
elif not(len(childs) == 1 and not child.get('id') and not child.get('first_name')):
child_errors = True
child['errors'] = {f: e[0] for f, e in child_form.errors.items()}
if not child_errors:
request.user.childs.exclude(id__in=childs_saved).delete()
form = self.get_form()
if form.is_valid() and not child_errors:
messages.info(request, 'Данные сохранены.')
response = self.form_valid(form)
# начисляем бонусы, если заполнил профиль
if self.object.phone and self.object.first_name and self.object.last_name and not self.object.bonuses.filter(
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count():
UserBonus.objects.create(user=self.object, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True,
action_name=UserBonus.ACTION_FILL_PROFILE)
return response
else:
context = self.get_context_data(**kwargs)
context['childs'] = childs
return self.render_to_response(context)
def get_success_url(self): def get_success_url(self):
return reverse('user-edit-profile') return reverse('user-edit-profile')

@ -59,8 +59,7 @@ class AutoAddingUserBonusTestCase(TestCase):
user = UserFactory.create(role=User.USER_ROLE, first_name='', last_name='', phone='') user = UserFactory.create(role=User.USER_ROLE, first_name='', last_name='', phone='')
self.assertEqual(user.bonus, 0, 'При создании есть бонусы') self.assertEqual(user.bonus, 0, 'При создании есть бонусы')
self.client.force_login(user) self.client.force_login(user)
data = {k: user.__dict__[k] for k in ['email', 'slug', 'child_first_name', data = {k: user.__dict__[k] for k in ['email', 'slug', 'city',
'child_last_name', 'child_birthday', 'child_gender', 'city',
'country', 'birthday', 'gender', 'about', 'instagram', 'facebook', 'country', 'birthday', 'gender', 'about', 'instagram', 'facebook',
'twitter', 'pinterest', 'youtube', 'vkontakte', 'site']} 'twitter', 'pinterest', 'youtube', 'vkontakte', 'site']}

@ -0,0 +1,119 @@
<template>
<div class="user-child-form form__group">
<div class="form__title">Карточка ребёнка</div>
<div class="user-child-form__description">
Вся информация конфиденциальна и не передается третьим лицам. Необходима только для персонализации наград,
поздравлений с Днем Рождения и других персонализированных акций.
</div>
<div class="tabs">
<div class="user-child-form__tabs tabs__nav">
<a class="user-child-form__tab tabs__btn" v-for="(child, index) in childs" :key="index" href="#"
v-bind:class="{active: selectedIndex == index}" @click.prevent="select(index)">
{{ child.first_name ? (child.first_name + '&nbsp;' + child.last_name) : ('Ребенок ' + index) }}</a>
<a href="#" class="tabs__btn" @click.prevent="add()">Добавить ребенка</a>
</div>
</div>
<div class="tabs__container">
<div v-for="(child, index) in childs" :key="index" class="tabs__item js-tabs-item"
v-bind:style="{display: selectedIndex == index ? 'block' : ''}">
<div class="form__fieldset">
<input type="hidden" name="child_id" :value="child.id" />
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.gender}">
<div class="field__label">ИМЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_first_name' class="field__input" type="text" placeholder="Имя"
v-model="child.first_name">
</div>
<div v-if="child.errors && child.errors.first_name" class="field__error">{{ child.errors.first_name }}</div>
</div>
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.last_name}">
<div class="field__label">ФАМИЛИЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_last_name' id="child-last-name" class="field__input" type="text" placeholder="Фамилия"
v-model="child.last_name">
</div>
<div v-if="child.errors && child.errors.last_name" class="field__error">{{ child.errors.last_name }}</div>
</div>
</div>
<div class="form__fieldset">
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.birthday}">
<div class="field__label">ДАТА РОЖДЕНИЯ</div>
<div class="field__wrap">
<vue-datepicker input-class="field__input" name="child_birthday" language="ru" format="dd/MM/yyyy"
v-model="child.birthday" placeholder="dd/mm/yyyy"/>
</div>
<div v-if="child.errors && child.errors.birthday" class="field__error">{{ child.errors.birthday }}</div>
</div>
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.gender}">
<div class="field__label">ПОЛ</div>
<div class="field__wrap">
<lil-select :value.sync="child.gender" :options="genders" value-key="0" title-key="1"/>
<input name='child_gender' type="hidden" v-model="child.gender">
</div>
<div v-if="child.errors && child.errors.gender" class="field__error">{{ child.errors.gender }}</div>
</div>
</div>
<a href="#" @click.prevent="remove(index)">Удалить</a>
</div>
</div>
</div>
</template>
<script>
import DatePicker from 'vuejs-datepicker';
import LilSelect from './inputs/LilSelect';
export default {
name: "childs",
props: ['childs'],
data() {
return {
selectedIndex: 0,
selectedChild: null,
genders: [
['n', 'М / Ж'],
['f', 'Ж'],
['m', 'М'],
]
}
},
methods: {
add(){
this.childs.push({
first_name: '',
last_name: '',
birthday: '',
errors: {},
});
this.select(this.childs.length - 1);
},
remove(index){
if(! confirm('Вы действительно хотите удалить данные о ребенке?')){
return;
}
this.childs.splice(index, 1);
if(! this.childs.length){
this.add();
}
else if(this.selectedIndex == index){
this.select(0);
}
},
select(index){
this.selectedIndex = index;
this.selectedChild = this.childs[index];
}
},
mounted(){
for(let i in this.childs){
if(this.childs[i].errors){
this.select(i);
}
}
},
components: {
'vue-datepicker': DatePicker,
LilSelect,
}
}
</script>

@ -2,6 +2,10 @@ import $ from 'jquery';
import slugify from 'slugify'; import slugify from 'slugify';
import ClipboardJS from 'clipboard'; import ClipboardJS from 'clipboard';
import {showNotification} from '../modules/notification'; import {showNotification} from '../modules/notification';
import Childs from '../../components/Childs.vue';
window.LIL_STORE.components['childs'] = Childs;
$(document).ready(function () { $(document).ready(function () {
if(window.LIL_STORE.urlIs('userBonuses')){ if(window.LIL_STORE.urlIs('userBonuses')){

@ -4705,12 +4705,24 @@ a
border-radius: 20px border-radius: 20px
box-shadow: 0 10px 24px 0 rgba(0, 0, 0, 0.05) box-shadow: 0 10px 24px 0 rgba(0, 0, 0, 0.05)
+t
margin: 30px -80px 30px -60px
+m
background: white
margin: 30px -80px 30px -60px
padding: 30px 80px 40px 60px
&__description &__description
margin-bottom: 40px margin-bottom: 40px
margin-top: -20px margin-top: -20px
color: #333333 color: #333333
font-size: 12px font-size: 12px
&__tabs
justify-content: left
overflow-x: auto
overflow-y: hidden
.bonuses-table .bonuses-table
margin-left: -120px margin-left: -120px

Loading…
Cancel
Save