diff --git a/accounts/admin.py b/accounts/admin.py index bd8a2251..d267f0d1 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -14,6 +14,7 @@ from models import User from forms import UserForm, UserCreationForm, ChangePasswordForm, EmailAnnouncementForm, UserFilterForm #custom views from functions.admin_views import AdminView, AdminListView +from django.views.generic import UpdateView class UserListView(AdminListView): template_name = 'admin/accounts/user_list.html' @@ -21,24 +22,35 @@ class UserListView(AdminListView): model = User +class EditUser(UpdateView): + model = User + form_class = UserForm + success_url = '/admin/accounts/all' + template_name = 'user_change.html' + + + def user_change(request, url): """ Return form of user and post it on the server. If form is posted redirect on the page of all users. """ - user = User.objects.safe_get(id=url) - # try get user by url if doesnt work by id - if user is None: - user = User.objects.safe_get(url=url) - #redirect to list of all users if cannot find user - if user is None: + try: + user = User.objects.get(url=url) + except User.DoesNotExist: + try: + user = User.objects.get(id=url) + except User.DoesNotExist, User.MultipleObjectsReturned: + return HttpResponseRedirect('/admin/accounts/all') + + except User.MultipleObjectsReturned: return HttpResponseRedirect('/admin/accounts/all') if request.POST: # bug with saving staff users(set is_staff to False) staff = user.is_staff - form = UserForm(request.POST, instance=user) + form = UserForm(request.POST, request.FILES, instance=user) if form.is_valid(): user = form.save() @@ -48,27 +60,29 @@ def user_change(request, url): user.save() return HttpResponseRedirect('/admin/accounts/all') else: - form.fields['city'].widget.attrs['data-init-text'] = user.profile.city.name + if user.profile.city: + form.fields['city'].widget.attrs['data-init-text'] = user.profile.city.name else: profile = user.profile data = {'country':profile.country_id, 'city': profile.city_id, 'title': profile.title, 'descriptions': profile.descriptions, 'keywords': profile.keywords, 'phone': profile.phone, 'web_page': profile.web_page, - 'about': profile.about} + 'about': profile.about, 'skype':profile.skype,'facebook':profile.facebook, 'linkedin':profile.linkedin, + 'twitter':profile.twitter, 'vk':profile.vk} form = UserForm(instance=user,initial=data) if user.profile.city: form.fields['city'].widget.attrs['data-init-text'] = user.profile.city.name - args = {} - args.update(csrf(request)) + context = {} + context.update(csrf(request)) - args['form'] = form - args['object'] = user + context['form'] = form + context['object'] = user - return render_to_response('user_change.html', args) + return render_to_response('user_change.html', context) def create_admin(request): if request.POST: diff --git a/accounts/admin_urls.py b/accounts/admin_urls.py index d4dc3cd3..225255e1 100644 --- a/accounts/admin_urls.py +++ b/accounts/admin_urls.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- from django.conf.urls import patterns, url -from admin import UserListView +from admin import UserListView, EditUser urlpatterns = patterns('', #url(r'^registration/$', 'accounts.admin.registration'), #url(r'^create_admin/$', 'accounts.admin.create_admin'), #url(r'^create_md5user/$', 'accounts.admin.create_md5'), - url(r'^change/(.*)/$', 'accounts.admin.user_change'), + # url(r'^change/(?P.*)/$', EditUser.as_view()), + url(r'^change/(?P.*)/$', 'accounts.admin.user_change'), url(r'^all/$', UserListView.as_view()), url(r'^reset_password_email/$', 'accounts.admin.reset_password_email'), ) \ No newline at end of file diff --git a/accounts/forms.py b/accounts/forms.py index 00c26f2f..395ac037 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -5,6 +5,7 @@ from django import forms from django.contrib.auth.forms import ReadOnlyPasswordHashField from django.forms.util import ErrorList from django.utils.translation import ugettext as _ +from django.conf import settings from models import User, Profile from theme.models import Theme, Tag from country.models import Area @@ -13,7 +14,7 @@ from country.models import Country from city.models import City from company.models import Company from organiser.models import Organiser -#functions +# functions from functions.form_check import translit_with_separator, is_latin @@ -27,7 +28,6 @@ def clean_relation_field(inst, field_name, model): return None - class UserCreationForm(forms.ModelForm): password1 = forms.CharField(label='Пароль', widget=forms.PasswordInput(render_value=False)) password2 = forms.CharField(label='Повторите пароль', widget=forms.PasswordInput(render_value=False)) @@ -36,7 +36,6 @@ class UserCreationForm(forms.ModelForm): model = User fields = ('email', 'first_name', 'last_name') - def clean_email(self): """ checking if user already exist @@ -56,7 +55,6 @@ class UserCreationForm(forms.ModelForm): raise forms.ValidationError('Пароли не совпадают') return password2 - def save(self, commit=True): user = super(UserCreationForm, self).save(commit=False) user.set_password(self.cleaned_data['password2']) @@ -66,6 +64,7 @@ class UserCreationForm(forms.ModelForm): return user + class UserChangeForm(forms.ModelForm): password = ReadOnlyPasswordHashField() @@ -76,24 +75,39 @@ class UserChangeForm(forms.ModelForm): return self.initial['password'] - class UserForm(forms.ModelForm): - #email = forms.EmailField(widget=forms.TextInput(attrs={'disabled' : True}), required=False) - country = forms.ChoiceField(label='Страна', choices=[(item.id, item.name) for item in Country.objects.all()], required=False) - city = forms.CharField(label='Город', widget=forms.HiddenInput()) - company = forms.ChoiceField(label='Компания', choices=[(item.id, item.name) for item in Company.objects.language().all()], required=False) - organiser = forms.ChoiceField(label='Организатор', choices=[(item.id, item.name) for item in Organiser.objects.language().all()], required=False) - title = forms.CharField(widget=forms.TextInput(attrs={'style':'width: 550px'}), required=False) - descriptions = forms.CharField(widget=forms.TextInput(attrs={'style':'width: 550px'}), required=False) - keywords = forms.CharField(widget=forms.TextInput(attrs={'style':'width: 550px'}), required=False) - phone = forms.CharField(widget=forms.TextInput(attrs={'style':'width: 550px'}), required=False) + # email = forms.EmailField(widget=forms.TextInput(attrs={'disabled' : True}), required=False) + country = forms.ChoiceField(label='Страна', choices=[(item.id, item.name) for item in Country.objects.all()], + required=False) + city = forms.CharField(label='Город', widget=forms.HiddenInput(), required=False) + company = forms.ChoiceField(label='Компания', + choices=[(item.id, item.name) for item in Company.objects.language().all()], + required=False) + organiser = forms.ChoiceField(label='Организатор', + choices=[(item.id, item.name) for item in Organiser.objects.language().all()], + required=False, initial=None) + title = forms.CharField(widget=forms.TextInput(attrs={'style': 'width: 550px'}), required=False) + descriptions = forms.CharField(widget=forms.TextInput(attrs={'style': 'width: 550px'}), required=False) + keywords = forms.CharField(widget=forms.TextInput(attrs={'style': 'width: 550px'}), required=False) + phone = forms.CharField(widget=forms.TextInput(attrs={'style': 'width: 550px'}), required=False) web_page = forms.URLField(required=False) - about = forms.CharField(widget=forms.Textarea()) + about = forms.CharField(widget=forms.Textarea(), required=False) + avatar = forms.ImageField(required=False) + # --- + skype = forms.CharField(required=False) + facebook = forms.URLField(required=False) + twitter = forms.URLField(required=False) + linkedin = forms.URLField(required=False) + vk = forms.URLField(required=False) + + new_password = forms.CharField(required=False, min_length=6, max_length=60, + widget=forms.TextInput(attrs={'class': 'new_password'})) class Meta: model = User - exclude = ('username', 'email','last_login', 'password', 'is_active', 'is_admin', 'is_superuser', 'is_staff' - 'date_joined', 'date_registered', 'date_modified') + exclude = ('username', 'email', 'last_login', 'password', 'is_admin', 'rating', 'is_superuser', 'is_staff' + 'date_joined', + 'date_registered', 'date_modified') def save(self, force_insert=False, force_update=False, commit=True): @@ -108,28 +122,34 @@ class UserForm(forms.ModelForm): profile.phone = data.get('phone') profile.web_page = data.get('web_page') profile.about = data.get('about') + profile.skype = data.get('skype', '') + profile.facebook = data.get('facebook', '') + profile.twitter = data.get('twitter', '') + profile.linkedin = data.get('linkedin', '') + profile.vk = data.get('vk', '') + profile.avatar = data.get('avatar') + + if data['new_password']: + new_pass = data['new_password'] + user.set_password(new_pass) + user.email_user('Reset password', 'Your new password: "%s" ' % new_pass, settings.DEFAULT_FROM_EMAIL, ) if commit: user.save() profile.save() return user - """ def clean_url(self): url = self.cleaned_data.get('url') - try: - user = User.objects.get(url=translit_with_separator(url)) - if (user.url == translit_with_separator(url)): + if url: + if User.objects.get(url=translit_with_separator(url)): + raise forms.ValidationError('Такой урл уже занят') + else: return url - except: - return url - raise forms.ValidationError('Такой урл уже занят') - """ def clean_organiser(self): return clean_relation_field(self, 'organiser', Organiser) - def clean_company(self): return clean_relation_field(self, 'company', Company) @@ -139,23 +159,23 @@ class UserForm(forms.ModelForm): def clean_city(self): return clean_relation_field(self, 'city', City) - def clean_phone(self): """ phone code checking """ cleaned_data = super(UserForm, self).clean() phone = cleaned_data.get('phone') - if not phone: + if not phone: return - deduct = ('-','(',')','.',' ') + deduct = ('-', '(', ')', '.', ' ') for elem in deduct: phone = phone.replace(elem, '') if phone.isdigit(): return phone else: raise forms.ValidationError('Введите правильный код страны') + """ def clean_web_page(self): cleaned_data = super(UserForm, self).clean() @@ -171,6 +191,7 @@ class UserForm(forms.ModelForm): return forms.ValidationError('Введите правильный адрес страници') """ + class ChangePasswordForm(forms.Form): """ Form to change password @@ -183,7 +204,8 @@ class ChangePasswordForm(forms.Form): attrs={'placeholder': _(u'Придумайте новый пароль')})) new_password_confirm = forms.CharField(label=_(u'Confirm password'), required=True, widget=forms.PasswordInput(render_value=False, - attrs={'placeholder': _(u'Повторите новый пароль')})) + attrs={ + 'placeholder': _(u'Повторите новый пароль')})) def clean(self): data = super(ChangePasswordForm, self).clean() @@ -191,7 +213,7 @@ class ChangePasswordForm(forms.Form): password2 = data.get('new_password_confirm') if not password1 or not password2: return data - # self._errors['new_password'] = ErrorList([_(u'Different passwords!')]) + # self._errors['new_password'] = ErrorList([_(u'Different passwords!')]) # return data if password1 and password2 and password1 != password2: # check if passwords exists and equal @@ -200,7 +222,7 @@ class ChangePasswordForm(forms.Form): del data['new_password_confirm'] del data['new_password'] return data - if not password1.isdigit() and any(char.isdigit() for char in password1) and len(password1)>5: + if not password1.isdigit() and any(char.isdigit() for char in password1) and len(password1) > 5: # password must contain digits and letters and length > 5 return data else: @@ -210,33 +232,35 @@ class ChangePasswordForm(forms.Form): del data['new_password_confirm'] return data + class EmailAnnouncementForm(forms.Form): data = [(1, _(u'Получать приглашения, сообщения и другую корреспонденцию от пользователей Expomap')), - (2, _(u'Получать обзор событий')), - (3, _(u'Получать новости'))] + (2, _(u'Получать обзор событий')), + (3, _(u'Получать новости'))] announcement = forms.MultipleChoiceField(choices=data, widget=forms.CheckboxSelectMultiple()) class RegistrationCompleteForm(forms.ModelForm): - country = forms.ModelChoiceField(label='Страна', queryset= Country.objects.all(), + country = forms.ModelChoiceField(label='Страна', queryset=Country.objects.all(), widget=forms.Select(attrs={'class': 'select2'})) city = forms.CharField(label='Город', widget=forms.HiddenInput()) url = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _(u'url(обязательно)')})) code_country = forms.ChoiceField(label=_(u'код страны'), initial='70', - choices=[(str(c.phone_code), '+'+str(c.phone_code)) for c in Country.objects.all() if c.phone_code is not None], + choices=[(str(c.phone_code), '+' + str(c.phone_code)) for c in + Country.objects.all() if c.phone_code is not None], widget=forms.Select(attrs={'class': 'select2'})) code_city = forms.CharField(label=_(u'код города')) phone = forms.CharField(label=_(u'ваш номер')) + class Meta: model = User fields = ('url',) - def save(self, force_insert=False, force_update=False, commit=True): user = super(RegistrationCompleteForm, self).save(commit=False) data = self.cleaned_data - phone = data['code_country']+data['code_city']+data['phone'] + phone = data['code_country'] + data['code_city'] + data['phone'] user.profile.phone = int(phone) user.profile.country = data['country'] @@ -253,7 +277,6 @@ class RegistrationCompleteForm(forms.ModelForm): except City.DoesNotExist: return None - def clean_url(self): url = self.cleaned_data['url'] if not is_latin(url): @@ -267,6 +290,36 @@ class RegistrationCompleteForm(forms.ModelForm): +class SocialRegistrationCompleteForm(RegistrationCompleteForm): + email = forms.EmailField(widget=forms.TextInput(attrs={'placeholder': _(u'Email')}), required=True) + + class Meta: + model = User + fields = ['email', 'url'] + + def save(self, force_insert=False, force_update=False, commit=True): + email = self.cleaned_data['email'] + if User.objects.filter(email=email).exists(): + # pass to the view user, that had account before + user = User.objects.get(email=email) + # social = UserSocialAuth.objects.get(user=self.instance) + # social.user = user # that cause AuthAlreadyAssociated error! + else: + # create new user, with data from social auth + user = super(RegistrationCompleteForm, self).save(commit=False) # creating new user + user.username = email + + data = self.cleaned_data + phone = data['code_country'] + data['code_city'] + data['phone'] + user.profile.phone = int(phone) + user.profile.country = data['country'] + user.profile.city = data['city'] + user.profile.save() + + # if commit: + user.save() + return user + class RecoveryForm(forms.Form): email = forms.EmailField(widget=forms.TextInput(attrs={'placeholder': _(u'Email')})) @@ -283,10 +336,13 @@ class RecoveryForm(forms.Form): raise forms.ValidationError(_(u'Пользователь с таким емейлом не зарегестрирован')) +from django.db.models import Q + class UserFilterForm(forms.Form): model = User - email = forms.CharField(label=_(u'Email'), required=False) + search_req = forms.CharField(label=_(u'Введите e-mail, имя или фамилию для запроса'), required=False) + def filter(self): """ @@ -295,27 +351,31 @@ class UserFilterForm(forms.Form): """ data = self.cleaned_data - email = data['email'] + search_req = data['search_req'] + words = search_req.split() model = self.model qs = model.objects.all() - if email: - qs = qs.filter(email__contains=email) + if len(words) > 1: + qs = qs.filter(Q(first_name__contains=words[0]) & Q(last_name__contains=words[1])) + elif words: + qs = qs.filter( + Q(email__contains=search_req) | Q(first_name__contains=search_req) | Q(last_name__contains=search_req)) return qs - class FeedFilterForm(forms.Form): data_with_parents = None filter = None th = forms.MultipleChoiceField(label=_(u'Тематика'), choices=[(item.id, item.name) for item in Theme.active.all()], - required=False, widget=forms.CheckboxSelectMultiple()) + required=False, widget=forms.CheckboxSelectMultiple()) tg = forms.CharField(label=_(u'Теги'), required=False, widget=forms.CheckboxSelectMultiple()) - area = forms.MultipleChoiceField(label=_(u'Регион'), choices=[(item.id, item.name) for item in Area.objects.language().all()], + area = forms.MultipleChoiceField(label=_(u'Регион'), + choices=[(item.id, item.name) for item in Area.objects.language().all()], required=False, widget=forms.CheckboxSelectMultiple()) co = forms.MultipleChoiceField(label=_(u'Страна'), required=False, widget=forms.CheckboxSelectMultiple(), choices=[(item.id, item.name) for item in Country.objects.expo_countries()] - ) + ) ci = forms.MultipleChoiceField(label=_(u'Город'), required=False, widget=forms.CheckboxSelectMultiple(), choices=[(item.id, item.name) for item in City.used.expo_cities()] @@ -336,21 +396,19 @@ class FeedFilterForm(forms.Form): self.filter = filter self.data_with_parents = self.get_form_data(filter) - def get_form_data(self, filter): - if filter.area.exists(): - areas = [{'name':'area', 'id':item.id, 'parent':None, 'text':item.name} for item in filter.area.all()] + areas = [{'name': 'area', 'id': item.id, 'parent': None, 'text': item.name} for item in filter.area.all()] else: areas = [] if filter.country.exists(): cos = [] for country in filter.country.all(): - cos.append({'name':'area', 'id':country.area_id, 'text': country.area.name, 'children':{ - 'id': country.id, 'name':'co', 'text': country.name - } + cos.append({'name': 'area', 'id': country.area_id, 'text': country.area.name, 'children': { + 'id': country.id, 'name': 'co', 'text': country.name + } }) else: cos = [] @@ -358,29 +416,27 @@ class FeedFilterForm(forms.Form): if filter.city.exists(): cis = [] for city in filter.city.all(): - cis.append({'name':'area', 'id': city.country.area_id, 'text': city.country.area.name, 'children':{ - 'id': city.country_id, 'name':'co', 'text': city.country.name, 'children':{ - 'name':'ci', 'id':city.id, 'text':city.name - } - } + cis.append({'name': 'area', 'id': city.country.area_id, 'text': city.country.area.name, 'children': { + 'id': city.country_id, 'name': 'co', 'text': city.country.name, 'children': { + 'name': 'ci', 'id': city.id, 'text': city.name + } + } }) else: cis = [] if filter.theme.exists(): - ths = [{'name':'th', 'id':item.id, 'parent':None, 'text':item.name} for item in filter.theme.all()] + ths = [{'name': 'th', 'id': item.id, 'parent': None, 'text': item.name} for item in filter.theme.all()] else: ths = [] if filter.tag.exists(): tgs = [] for tag in filter.tag.all(): - - - tgs.append({'name':'th', 'id':tag.theme_id, 'text': tag.theme.name, 'children':{ - 'id': tag.id, 'name':'tg', 'text': tag.name - } + tgs.append({'name': 'th', 'id': tag.theme_id, 'text': tag.theme.name, 'children': { + 'id': tag.id, 'name': 'tg', 'text': tag.name + } }) else: tgs = [] @@ -418,4 +474,4 @@ class FeedFilterForm(forms.Form): if tg: res = ast.literal_eval(tg) return res - return tg \ No newline at end of file + return tg diff --git a/accounts/models.py b/accounts/models.py index 43125dcc..ce4e4b47 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -32,7 +32,8 @@ class UserManager(BaseUserManager): raise ValueError('Вы должни ввести электронную почту') user= self.model( - email = UserManager.normalize_email(email),first_name = first_name,last_name = last_name, + email = UserManager.normalize_email(email), + first_name = first_name,last_name = last_name, username = UserManager.normalize_email(email), is_staff=False, is_active=False, is_superuser=False, last_login=now, date_joined=now, **extra_fields @@ -42,7 +43,7 @@ class UserManager(BaseUserManager): user.save(using=self._db) return user - def create_social_user(self,username, first_name, last_name, password=None, **extra_fields): + def create_social_user(self, username, first_name, last_name, password=None, **extra_fields): now = timezone.now() # generate random password digits = random.sample(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), 4) @@ -116,7 +117,7 @@ class User(AbstractBaseUser, PermissionsMixin): first_name = models.CharField(verbose_name='First name', max_length=255) last_name = models.CharField(verbose_name='Last name', max_length=255) rating = models.IntegerField(default=100)# добавить индекс в базе - url = models.SlugField(blank=True) + url = models.SlugField(blank=True)#, unique=True, null=True) # is_active = models.BooleanField(default=0) # СДЕЛАТЬ проверку на емейле is_staff = models.BooleanField(default=0) @@ -382,8 +383,10 @@ def calculate_rating(user): user_rating_fields = {'position': 5, 'company': 5, 'url': 10} profile_rating_fields = {'country': 5, 'city': 5, 'phone': 10, 'facebook': 5, 'twitter': 5, 'linkedin': 5, 'vk': 5, 'web_page': 10, 'avatar': 20, 'about': 15} - # доделать "Отметки на выставках, за каждую", "Подписка на рассылку", "Добавление фото, за каждую", "Добавление компании, за каждую опубликованную" - + ''' + TODO: доделать "Отметки на выставках, за каждую", "Подписка на рассылку", "Добавление фото, за каждую", + "Добавление компании, за каждую опубликованную" + ''' # base rating rating = 100 for key, value in user_rating_fields.iteritems(): diff --git a/accounts/urls.py b/accounts/urls.py index ffd93efa..2345511f 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -16,6 +16,7 @@ urlpatterns = patterns('', url(r'^profile/company/$', login_required(ProfileCompanyView.as_view())), url(r'^profile/settings/$', login_required(SettingsView.as_view())), url(r'^profile/calendar/remove/$', 'accounts.views.remove_from_calendar'), + url(r'^profile/calendar/export/$', 'core.views.download_workbook'), url(r'^profile/calendar/$', login_required(CalendarView.as_view())), url(r'^profile/feed/page/(?P\d+)/$', Feed.as_view()), url(r'^profile/feed/$', login_required(Feed.as_view())), @@ -28,6 +29,7 @@ urlpatterns = patterns('', url(r'^user/(?P.*)/events/(?P\d+)/$', UserView.as_view()), url(r'^user/(?P.*)/events/$', UserView.as_view()), url(r'^user/(?P.*)/$', UserView.as_view()), + url(r'^inactive-user/$', 'registration.backends.default.views.inactive_user_message'), #url(r'^profile/messages/(?P.*)/$', login_required(MessagesView.as_view())), #url(r'^profile/messages/$', login_required(MessagesView.as_view())), diff --git a/core/admin_urls.py b/core/admin_urls.py new file mode 100644 index 00000000..81e844eb --- /dev/null +++ b/core/admin_urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url, patterns +from views import NewPage, PageList, EditPage, DeletePage, PageDetailed + +urlpatterns = patterns('', + url(r'^new/$', NewPage.as_view(), name='new_page' ), + url(r'^all/$', PageList.as_view(), name = 'page_list'), + url(r'^edit/(?P.*)/$', EditPage.as_view(), name='edit_page'), + url(r'^delete/(?P.*)/$', DeletePage.as_view(), name='delete_page'), + url(r'^(?P.*)/$', PageDetailed.as_view(), name='page_view'), +) diff --git a/core/forms.py b/core/forms.py index baf0a04b..6244f97e 100644 --- a/core/forms.py +++ b/core/forms.py @@ -6,7 +6,8 @@ from place_exposition.models import PlaceExposition from place_conference.models import PlaceConference from django.utils.translation import ugettext_lazy as _ from haystack.query import SearchQuerySet, EmptySearchQuerySet - +from hvad.forms import TranslatableModelForm +from models import Page class PlaceSearchForm(forms.Form): q = forms.CharField(label=_(u'Поиск'), required=False) @@ -45,3 +46,38 @@ class CallbackForm(forms.Form): def send(self): phone = self.cleaned_data['callback_phone'] send_mail(phone, phone, None, [settings.CALLBACK_EMAIL]) + + +# ------------------ Page Form ----------------------- + +from django.conf import settings +from django.forms import Textarea +from ckeditor.widgets import CKEditorWidget + + + +class PageForm(TranslatableModelForm): + # language = 'ru' + + class Meta: + model = Page + fields = ['url','title','h1','descriptions','keywords', 'body' ] + widgets = { + 'body':CKEditorWidget, + 'keywords':Textarea, + 'descriptions':Textarea, + } + def clean_url(self): + url = self.cleaned_data.get('url', None) + if url[0] == '/': + url = url[1:] + if url[-1] == '/': + url = url[:-1] + if ' ' in url: + url.replace(' ', '_') + return url + + + + + diff --git a/core/models.py b/core/models.py index dad86ae2..268a7009 100644 --- a/core/models.py +++ b/core/models.py @@ -1,12 +1,18 @@ # -*- coding: utf-8 -*- from django.contrib.syndication.views import Feed from django.shortcuts import get_object_or_404 +from django.db import models from exposition.models import Exposition +from settings.models import create_transl_fields from theme.models import Theme from country.models import Country from city.models import City +from hvad.models import TranslatableModel, TranslatedFields + +# ----------------------- RSS -------------------------- # + EXPO_ON_PAGE = 10 # nearest expositions at all @@ -85,4 +91,47 @@ class ThemeFeeds(Feed): return obj.main_title def items(self, obj): - return Exposition.enable.upcoming().filter(theme = obj)[:NUM_ITEMS_ON_PAGE] \ No newline at end of file + return Exposition.enable.upcoming().filter(theme = obj)[:NUM_ITEMS_ON_PAGE] + +# ------------------------ Page model --------------------------- # +from django.conf import settings +from django.core.urlresolvers import reverse + +class Page(TranslatableModel): + + url = models.SlugField(unique=True) + translations = TranslatedFields( + h1 = models.CharField(max_length=255), + body = models.TextField(), + # meta + title = models.CharField(max_length=255, blank=True), + descriptions = models.CharField(max_length=255, blank=True), + keywords = models.CharField(max_length=255, blank=True), + ) + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + def get_absolute_url(self): + return reverse('page_view', args=[self.url]) + + def save(self, *args, **kwargs): + super(Page,self).save(*args, **kwargs) + + all_field_names = list(self._translated_field_names) + clear_f_n = [] + for field_name in all_field_names: + if field_name not in ['master', 'master_id',u'id', 'language_code']: + clear_f_n.append(field_name) + field_items = {field_name:getattr(self, field_name) for field_name in clear_f_n} + + langs = [lan[0] for lan in settings.LANGUAGES] + for lang in langs: + if lang not in self.get_available_languages(): + self.translate(lang) + for field in clear_f_n: + setattr(self, field, field_items.get(field, '')) + obj = super(Page,self).save(*args, **kwargs) + return obj + + def __unicode__(self): + return self.url diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 00000000..ee429eee --- /dev/null +++ b/core/utils.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +import xlwt +import datetime + +from exposition.models import Exposition +from conference.models import Conference + +from django.core.exceptions import ObjectDoesNotExist + +HEADER_STYLE = xlwt.easyxf('font: bold on') +DEFAULT_STYLE = xlwt.easyxf() +CELL_STYLE_MAP = ( + (datetime.date, xlwt.easyxf(num_format_str='DD/MM/YYYY')), + (datetime.time, xlwt.easyxf(num_format_str='HH:MM')), + (bool, xlwt.easyxf(num_format_str='BOOLEAN')), +) + +def multi_getattr(obj, attr, default=None): + attributes = attr.split(".") + for i in attributes: + try: + obj = getattr(obj, i) + except AttributeError: + if default: + return default + else: + return '-' + return obj + + +def get_column_cell(obj, name): + try: + attr = multi_getattr(obj, name) + except ObjectDoesNotExist: + return '' + if hasattr(attr, '_meta'): + # A Django Model (related object) + return unicode(attr).strip() + elif hasattr(attr, 'all'): + # A Django queryset (ManyRelatedManager) + return ', '.join(unicode(x).strip() for x in attr.all()) + return attr + +def queryset_to_workbook(queryset, columns, header_style=None, default_style=None, cell_style_map=None): + workbook = xlwt.Workbook() + report_date = datetime.date.today() + sheet_name = 'Export {0}'.format(report_date.strftime('%Y-%m-%d')) + sheet = workbook.add_sheet(sheet_name) + + if not header_style: + header_style = HEADER_STYLE + if not default_style: + default_style = DEFAULT_STYLE + if not cell_style_map: + cell_style_map = CELL_STYLE_MAP + + obj = queryset[0] + + for y, column in enumerate(columns): + header_list=[u'Название события',u'Страна',u'Город',u'Место проведения', u'Дата начала', u'Дата окончания'] + + sheet.write(0, y, header_list[y], header_style) + + for x, obj in enumerate(queryset, start=1): + for y, column in enumerate(columns): + value = get_column_cell(obj, column) + style = default_style + for value_type, cell_style in cell_style_map: + if isinstance(value, value_type): + style = cell_style + sheet.write(x, y, value, style) + + return workbook + + diff --git a/core/views.py b/core/views.py index 8683d1ab..9545b925 100644 --- a/core/views.py +++ b/core/views.py @@ -4,7 +4,8 @@ from country.models import Country from city.models import City from place_exposition.models import PlaceExposition from place_conference.models import PlaceConference -from django.views.generic import ListView, FormView +from django.views.generic import ListView, CreateView, DeleteView, UpdateView, DetailView +from django.core.urlresolvers import reverse_lazy from functions.views_help import split_params from django.utils.translation import ugettext as _ @@ -199,3 +200,83 @@ class PlacePhotoView(PlaceListView): context = super(PlacePhotoView, self).get_context_data(**kwargs) context['object'] = self.obj return context + + +# --------------------- Page views ------------------------ + +from forms import PageForm +from models import Page + +class NewPage(CreateView): + form_class= PageForm + template_name = 'new_page.html' + success_url = '/admin/page/all/' + + +class PageList(ListView): + paginate_by = 10 + template_name = 'page_admin_list.html' + model = Page + order = 'created' +from django import forms +from django.http import HttpResponseRedirect +class EditPage(UpdateView): + model = Page + template_name = 'new_page.html' + form_class = PageForm + slug_url_kwarg = 'url' + slug_field = 'url' + + def get_context_data(self, **kwargs): + context = super(EditPage,self).get_context_data(**kwargs) + context['request'] = self.request + return context + + def get_success_url(self): + return HttpResponseRedirect('/admin/page/all/') + +class DeletePage(DeleteView): + template_name = 'admin/page/page_confirm_delete.html' + model = Page + success_url = reverse_lazy('page_list') + slug_field = 'url' + slug_url_kwarg = 'url' + +class PageDetailed(DetailView): + model = Page + template_name = 'client/base_page.html' + slug_field = 'url' + slug_url_kwarg = 'url' + + + + + +# ------------ XLS Export ---------------- + +from django.http import HttpResponse +from .utils import queryset_to_workbook +from exposition.models import Exposition +from conference.models import Conference + +def download_workbook(request): + data = [(36539, 'expo'),(36602, 'expo'), (3033, 'conf'), (3053, 'conf')] + qs = [] + for obj in data: + if obj[1] == 'expo': + qs.append(Exposition.objects.get(id=obj[0])) + if obj[1] == 'conf': + qs.append(Conference.objects.get(id=obj[0])) + columns = ( + 'name', + 'country.name', + 'city.name', + 'place.name', + 'data_begin', + 'data_end') + + workbook = queryset_to_workbook(qs, columns) + response = HttpResponse(content_type='application/vnd.ms-excel') + response['Content-Disposition'] = 'attachment; filename="export.xls"' + workbook.save(response) + return response diff --git a/country/manager.py b/country/manager.py index fde5ca56..1d4b278e 100644 --- a/country/manager.py +++ b/country/manager.py @@ -7,13 +7,13 @@ from hvad.models import TranslationManager class CountryManager(TranslationManager): cache_time = 600 - + ''' def all(self): """ hack """ return super(TranslationManager, self).all().filter(translations__language_code=lang()).order_by('translations__name') - + ''' def safe_get(self, **kwargs): model = self.model try: diff --git a/expobanner/__init__.py b/expobanner/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/expobanner/admin.py b/expobanner/admin.py new file mode 100644 index 00000000..5bb709d7 --- /dev/null +++ b/expobanner/admin.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +from django.views.generic import TemplateView, CreateView, ListView, UpdateView, DetailView +from django.conf import settings +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from expobanner.models import URL, BannerGroup, Banner, Paid +from expobanner.forms import UrlCreateForm, BannerCreateGroupForm, BannerCreateForm, BannerGroupUpdateForm,\ + PaidCreateForm, PaidUpdateForm, TopCreateForm +from exposition.models import Exposition + + +class BannersControl(TemplateView): + template_name = 'admin/expobanner/banners_control.html' + +# CREATE VIEWS +class AbstractCreate(CreateView): + template_name = 'admin/expobanner/default_form.html' + success_url = '/admin/expobanners/banners/control/' + + +class CreateUrl(AbstractCreate): + model = URL + form_class = UrlCreateForm + + +class CreateBannerGroup(AbstractCreate): + model = BannerGroup + form_class = BannerCreateGroupForm + + +class CreateBanner(AbstractCreate): + model = Banner + form_class = BannerCreateForm + + +# LISTS VIEWS +class AbstractList(ListView): + paginate_by = settings.ADMIN_PAGINATION + template_name = 'admin/expobanner/default_list.html' + + def get_context_data(self, **kwargs): + context = super(AbstractList, self).get_context_data(**kwargs) + context['verbose'] = self.verbose + return context + +class UrlList(AbstractList): + model = URL + verbose = u'Список урлов' + + +class BannerGroupList(AbstractList): + model = BannerGroup + verbose = u'Список груп' + + +class BannerList(AbstractList): + model = Banner + verbose = u'Список банеров' + template_name = 'admin/expobanner/banner_list.html' + + def get_queryset(self): + qs = super(BannerList, self).get_queryset() + qs = qs.filter(group__isnull=False) + return qs + +# UPDATE VIEWS +class AbstractUpdate(UpdateView): + template_name = 'admin/expobanner/default_form.html' + success_url = '/admin/expobanners/banners/control/' + + +class UrlUpdate(AbstractUpdate): + model = URL + form_class = UrlCreateForm + + +class BannerGroupUpdate(AbstractUpdate): + model = BannerGroup + form_class = BannerGroupUpdateForm + + +class BannerUpdate(AbstractUpdate): + model = Banner + form_class = BannerCreateForm + + +class BannerStat(DetailView): + model = Banner + template_name = 'admin/expobanner/banner_stat.html' + +class PaidList(ListView): + model = Exposition + template_name = 'admin/expobanner/paid_list.html' + paginate_by = settings.ADMIN_PAGINATION + + def get_queryset(self): + return self.model.objects.language().filter(paid_new__isnull=False) + +class PaidCreate(CreateView): + form_class = PaidCreateForm + template_name = 'admin/expobanner/paid_create.html' + success_url = '/admin/expobanners/paid/list/' + +class PaidUpdate(UpdateView): + model = Paid + form_class = PaidUpdateForm + template_name = 'admin/expobanner/paid_update.html' + success_url = '/admin/expobanners/paid/list/' + + def get_initial(self): + """ + Returns the initial data to use for forms on this view. + """ + initial = super(PaidUpdate, self).get_initial() + obj = self.object + initial['tickets'] = obj.tickets.url + initial['participation'] = obj.participation.url + initial['official'] = obj.official.url + + return initial + + def get_context_data(self, **kwargs): + context = super(PaidUpdate, self).get_context_data(**kwargs) + obj = self.object + context['exposition'] = obj.get_event() + return context + + +def paid_turn(request, pk, status): + paid = get_object_or_404(Paid, pk=pk) + if status == 'on': + paid.public = True + else: + paid.public = False + paid.save() + return HttpResponseRedirect('/admin/expobanners/paid/list/') + + +class PaidStat(DetailView): + model = Paid + template_name = 'admin/expobanner/paid_stat.html' + + +class TopList(ListView): + model = Exposition + template_name = 'admin/expobanner/top_list.html' + paginate_by = settings.ADMIN_PAGINATION + + def get_queryset(self): + return self.model.objects.language().filter(top__isnull=False) + + +class TopCreate(CreateView): + form_class = TopCreateForm + template_name = 'admin/expobanner/top_create.html' + success_url = '/admin/expobanners/top/list/' diff --git a/expobanner/admin_urls.py b/expobanner/admin_urls.py new file mode 100644 index 00000000..5497da30 --- /dev/null +++ b/expobanner/admin_urls.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import patterns, url +from expobanner.admin import * + +urlpatterns = patterns('expobanner.admin', + # banners + url(r'^banners/control/$', BannersControl.as_view(), name='expobanner-baneers_control'), + url(r'^banners/url/$', CreateUrl.as_view(), name='expobanner-create_url'), + url(r'^banners/group/$', CreateBannerGroup.as_view(), name='expobanner-create_group'), + url(r'^banners/banner/$', CreateBanner.as_view(), name='expobanner-create_banner'), + url(r'^banners/url/list/$', UrlList.as_view(), name='expobanner-list_url'), + url(r'^banners/group/list/$', BannerGroupList.as_view(), name='expobanner-list_group'), + url(r'^banners/banner/list/$', BannerList.as_view(), name='expobanner-list_banner'), + url(r'^banners/url/(?P\d+)/edit/$', UrlUpdate.as_view(), name='expobanner-update_url'), + url(r'^banners/group/(?P\d+)/edit/$', BannerGroupUpdate.as_view(), name='expobanner-update_group'), + url(r'^banners/banner/(?P\d+)/edit/$', BannerUpdate.as_view(), name='expobanner-update_banner'), + url(r'^banners/banner/(?P\d+)/stat/$', BannerStat.as_view(), name='expobanner_stat_banner'), + # paid + url(r'^paid/list/$', PaidList.as_view(), name='expobanner-list_paid'), + url(r'^paid/(?P\d+)/edit/$', PaidUpdate.as_view(), name='expobanner-update_paid'), + url(r'^paid/$', PaidCreate.as_view(), name='expobanner-create_paid'), + url(r'^paid/turn/(?P\d+)/(?P.*)/$', paid_turn, name='expobanner-paid-turn'), + url(r'^paid/(?P\d+)/stat/$', PaidStat.as_view(), name='expobanner_stat_paid'), + # top + url(r'^top/list/$', TopList.as_view(), name='expobanner-list_top'), + url(r'^top/(?P\d+)/edit/$', PaidUpdate.as_view(), name='expobanner-update_top'), + url(r'^top/$', TopCreate.as_view(), name='expobanner-create_top'), + url(r'^top/(?P\d+)/stat/$', PaidStat.as_view(), name='expobanner_stat_top'), +) \ No newline at end of file diff --git a/expobanner/forms.py b/expobanner/forms.py new file mode 100644 index 00000000..f489d731 --- /dev/null +++ b/expobanner/forms.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +from django import forms +from expobanner.models import URL, BannerGroup, Banner, Paid, Top +from exposition.models import Exposition +from country.models import Country +from city.models import City +from theme.models import Theme, Tag + + +class UrlCreateForm(forms.ModelForm): + verbose = u'Создать урл' + class Meta: + model = URL + exclude = ['created_at', 'updated_at', 'sites'] + + +class BannerCreateGroupForm(forms.ModelForm): + verbose = u'Создать групу' + class Meta: + model = BannerGroup + exclude = ['created_at', 'updated_at', 'speed'] + +class BannerGroupUpdateForm(BannerCreateGroupForm): + verbose = u'Изменить групу' + class Meta: + model = BannerGroup + exclude = ['created_at', 'updated_at', 'slug', 'speed'] + + +class BannerCreateForm(forms.ModelForm): + verbose = u'Создать банер' + #country = forms.ChoiceField(label=u'Страна', choices=[('', ' ')] + [(c.id, c.name) for c in Country.objects.all()], required=False) + #theme = forms.ChoiceField(label=u'Тематика', required=False, + # choices=[('', ' ')] + [(item.id, item.name) for item in Theme.objects.language().all()]) + #city = forms.CharField(label=u'Город', widget=forms.HiddenInput(), required=False) + #tag = forms.CharField(label=u'Тег', widget=forms.HiddenInput(), required=False) + + class Meta: + model = Banner + exclude = ['created_at', 'updated_at', 'often', 'paid', 'stat_pswd'] + + +class ClientStatForm(forms.Form): + stat_pswd = forms.CharField(label=u'Введите пароль:') + + def check_pass(self, obj): + pswd = self.cleaned_data['stat_pswd'] + return obj.stat_pswd == pswd + + +class PaidCreateForm(forms.ModelForm): + verbose = u'Создать проплаченую выставку' + exposition = forms.CharField(label=u'Выставка', widget=forms.HiddenInput()) + tickets = forms.URLField(label=u'Линк на билеты') + participation = forms.URLField(label=u'Линк на участие') + official = forms.URLField(label=u'Линк на официальный сайт') + + class Meta: + model = Paid + fields = ['logo', 'organiser', 'public'] + + def save(self, commit=True): + paid = super(PaidCreateForm, self).save(commit=False) + if commit: + expo = self.cleaned_data['exposition'] + tickets = self.cleaned_data['tickets'] + tickets_link = Banner.objects.create_for_paid(expo, tickets, 'tickets') + participation = self.cleaned_data['participation'] + participation_link = Banner.objects.create_for_paid(expo, participation, 'participation') + official = self.cleaned_data['official'] + official_link = Banner.objects.create_for_paid(expo, official, 'official') + catalog = expo.get_permanent_url() + catalog_link = Banner.objects.create_for_paid(expo, catalog, 'catalog') + + + paid.tickets = tickets_link + paid.participation = participation_link + paid.official = official_link + paid.catalog = catalog_link + paid.save() + + expo.paid_new = paid + expo.save() + return paid + + def clean_exposition(self): + expo_id = self.cleaned_data['exposition'] + try: + expo = Exposition.objects.get(id=expo_id) + except Exposition.DoesNotExist: + raise forms.ValidationError(u'Такой выставки не существует') + return expo + +class PaidUpdateForm(forms.ModelForm): + tickets = forms.URLField(label=u'Линк на билеты') + participation = forms.URLField(label=u'Линк на участие') + official = forms.URLField(label=u'Линк на официальный сайт') + + class Meta: + model = Paid + fields = ['logo', 'organiser', 'public'] + + def save(self, commit=True): + paid = super(PaidUpdateForm, self).save(commit=False) + if commit: + tickets = self.cleaned_data['tickets'] + b_tickets = paid.tickets + if tickets != b_tickets.url: + b_tickets.url = tickets + b_tickets.save() + + participation = self.cleaned_data['participation'] + b_participation = paid.participation + if participation != b_participation.url: + b_participation.url = participation + b_participation.save() + + official = self.cleaned_data['official'] + b_official = paid.official + if official != b_official.url: + b_official.url = official + b_official.save() + + paid.save() + + return paid + + +class TopCreateForm(forms.ModelForm): + verbose = u'Создать выставку в топе' + exposition = forms.CharField(label=u'Выставка', widget=forms.HiddenInput()) + country = forms.MultipleChoiceField(label=u'Страна', choices=[('', ' ')] + [(c.id, c.name) for c in Country.objects.all()], required=False) + theme = forms.MultipleChoiceField(label=u'Тематика', required=False, + choices=[('', ' ')] + [(item.id, item.name) for item in Theme.objects.language().all()]) + excluded_cities = forms.CharField(label=u'Город', widget=forms.HiddenInput(), required=False) + excluded_tags = forms.CharField(label=u'Тег', widget=forms.HiddenInput(), required=False) + class Meta: + model = Top + fields = ['catalog', 'position', 'theme', 'excluded_tags', 'country', 'excluded_cities', 'fr', 'to'] + + def save(self, commit=True): + top = super(TopCreateForm, self).save(commit=False) + # Prepare a 'save_m2m' method for the form, + old_save_m2m = self.save_m2m + + def save_m2m(): + old_save_m2m() + # This is where we actually link the pizza with toppings + top.theme.clear() + for theme in self.cleaned_data['theme']: + top.theme.add(theme) + + self.save_m2m = save_m2m + + if commit: + expo = self.cleaned_data['exposition'] + top.save() + self.save_m2m() + expo.top = top + expo.save() + return top + + def clean_theme(self): + theme_ids = self.cleaned_data['theme'] + if theme_ids: + return Theme.objects.filter(id__in=theme_ids) + return None + + def clean_country(self): + country_ids = self.cleaned_data['country'] + if country_ids: + return Country.objects.filter(id__in=country_ids) + return None + + def clean_exposition(self): + expo_id = self.cleaned_data['exposition'] + try: + expo = Exposition.objects.get(id=expo_id) + except Exposition.DoesNotExist: + raise forms.ValidationError(u'Такой выставки не существует') + return expo \ No newline at end of file diff --git a/expobanner/management/__init__.py b/expobanner/management/__init__.py new file mode 100644 index 00000000..13ef41a7 --- /dev/null +++ b/expobanner/management/__init__.py @@ -0,0 +1 @@ +__author__ = 'kotzilla' diff --git a/expobanner/management/commands/__init__.py b/expobanner/management/commands/__init__.py new file mode 100644 index 00000000..13ef41a7 --- /dev/null +++ b/expobanner/management/commands/__init__.py @@ -0,0 +1 @@ +__author__ = 'kotzilla' diff --git a/expobanner/management/commands/banner_log_check_previous_day.py b/expobanner/management/commands/banner_log_check_previous_day.py new file mode 100644 index 00000000..3a6a1f10 --- /dev/null +++ b/expobanner/management/commands/banner_log_check_previous_day.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from datetime import date, timedelta +from django.core.management.base import BaseCommand +from expobanner.models import Log, LogStat, Banner +from django.conf import settings + + +class Command(BaseCommand): + def handle(self, *args, **options): + prev_day = date.today() - timedelta(days=1) + for banner in Banner.objects.select_related('group').filter(): + try: + logstat = LogStat.objects.get(banner=banner, group=banner.group, date=prev_day) + except LogStat.DoesNotExist: + logstat = LogStat(banner=banner, group=banner.group, date=prev_day) + + views = Log.objects.filter(datetime__startswith=prev_day, + banner=banner, + group=banner.group, + type=1).count() + clicks = Log.objects.filter(datetime__startswith=prev_day, + banner=banner, + group=banner.group, + type=2).count() + unique_views = Log.objects.filter(datetime__startswith=prev_day, + banner=banner, + group=banner.group, + type=1).values('ip').distinct().count() + unique_clicks = Log.objects.filter(datetime__startswith=prev_day, + banner=banner, + group=banner.group, + type=2).values('ip').distinct().count() + + if not logstat.click or logstat.click < clicks: + logstat.click = clicks + if not logstat.view or logstat.view < views: + logstat.view = views + if not logstat.unique_click or logstat.unique_click < unique_clicks: + logstat.unique_click = unique_clicks + if not logstat.unique_view or logstat.unique_view < unique_views: + logstat.unique_view = unique_views + logstat.save() diff --git a/expobanner/management/commands/banner_log_update.py b/expobanner/management/commands/banner_log_update.py new file mode 100644 index 00000000..86cfb920 --- /dev/null +++ b/expobanner/management/commands/banner_log_update.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +from datetime import date +from django.core.management.base import BaseCommand +from expobanner.models import Log, LogStat, Banner, PaidStat +from exposition.models import Exposition + + +class Command(BaseCommand): + def handle(self, *args, **options): + today = date.today() + # banners + for banner in Banner.objects.select_related('group').filter(public=True, group__isnull=False): + try: + logstat = LogStat.objects.get(banner=banner, group=banner.group, date=today) + except LogStat.DoesNotExist: + logstat = LogStat(banner=banner, group=banner.group, date=today) + + views = Log.objects.filter(datetime__startswith=today, + banner=banner, + group=banner.group, + type=1).count() + clicks = Log.objects.filter(datetime__startswith=today, + banner=banner, + group=banner.group, + type=2).count() + unique_views = Log.objects.filter(datetime__startswith=today, + banner=banner, + group=banner.group, + type=1).values('ip').distinct().count() + unique_clicks = Log.objects.filter(datetime__startswith=today, + banner=banner, + group=banner.group, + type=2).values('ip').distinct().count() + logstat.click = clicks + logstat.view = views + logstat.unique_click = unique_clicks + logstat.unique_view = unique_views + logstat.save() + + # paid expos + expos = list(Exposition.objects.select_related('paid_new').filter(paid_new__isnull=False)) + for expo in expos: + paid = expo.paid_new + try: + paidstat = PaidStat.objects.get(paid=paid, date=today) + except PaidStat.DoesNotExist: + paidstat = PaidStat(paid=paid, date=today) + + t_clicks = Log.objects.filter(datetime__startswith=today, banner=paid.tickets, type=2).count() + p_clicks = Log.objects.filter(datetime__startswith=today, banner=paid.participation, type=2).count() + o_clicks = Log.objects.filter(datetime__startswith=today, banner=paid.official, type=2).count() + c_clicks = Log.objects.filter(datetime__startswith=today, banner=paid.catalog, type=2).count() + + paidstat.tickets_clicks = t_clicks + paidstat.participation_clicks = p_clicks + paidstat.official_clicks = o_clicks + paidstat.catalog_clicks = c_clicks + + paidstat.save() + + + + + + + + diff --git a/expobanner/managers.py b/expobanner/managers.py new file mode 100644 index 00000000..42e82670 --- /dev/null +++ b/expobanner/managers.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -* +from datetime import date +from random import choice, shuffle +from django.db import models +from django.db.models import Q +from django.core.cache import cache + + +class BiasedManager(models.Manager): + def by_time(self, **kwargs): + all = super(BiasedManager, self).get_query_set().filter(**kwargs) + result = [] + for i in all: + for j in range(i.often): + result.append(i) + return result + + def one(self, **kwargs): + return choice(self.by_time(**kwargs)) + + def by_often(self, **kwargs): + result = self.by_time(**kwargs) + shuffle(result) + return result + + def create_for_paid(self, expo, url, role): + try: + name = str(expo.name) + except UnicodeEncodeError, UnicodeDecodeError: + name = expo.url + + alt = u'%s_%s'%(name, role) + return self.create(alt=alt, url=url, paid=True) + +class BannerGroupCached(models.Manager): + def all(self): + key = 'banner_group_all' + result = cache.get(key) + if not result: + result = list(self.filter(public=True)) + cache.set(key, result, 90) + return result + + def group_banners(self): + key = 'banner_group_banners' + result = cache.get(key) + if not result: + groups = self.all() + today = date.today() + result = {} + for group in groups: + result[group.slug] = list(group.banners.prefetch_related('urls', 'theme', 'country')\ + .filter(public=True, fr__lte=today)\ + .filter(Q(to__gte=today) | Q(to__isnull=True)).extra({})) + cache.set(key, result, 70) + + return result + + +class URLCached(models.Manager): + def all(self): + key = 'banner_url_all' + result = cache.get(key) + if not result: + result = list(self.filter(public=True)) + cache.set(key, result, 150) + return result + +class TopCached(models.Manager): + def all(self): + key = 'expo_b_top_all' + result = cache.get(key) + if not result: + result = list(self.prefetch_related('theme', 'country', 'excluded_tags', 'excluded_cities').all()) + cache.set(key, result, 80) + + return result \ No newline at end of file diff --git a/expobanner/mixins.py b/expobanner/mixins.py new file mode 100644 index 00000000..9520ab37 --- /dev/null +++ b/expobanner/mixins.py @@ -0,0 +1,3 @@ +class StatMixin(object): + def get_cookie_name(self): + return u'%s_%d'%(self._meta.db_table, self.id) \ No newline at end of file diff --git a/expobanner/models.py b/expobanner/models.py new file mode 100644 index 00000000..e543b29e --- /dev/null +++ b/expobanner/models.py @@ -0,0 +1,294 @@ +# -*- coding: utf-8 -*- +import random +import hashlib +from datetime import datetime, date +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings +from django.contrib.sites.models import Site +from django.db.models.signals import post_save +from .managers import BiasedManager, BannerGroupCached, URLCached, TopCached +from .mixins import StatMixin +from theme.models import Theme +from country.models import Country + + +class URL(models.Model): + title = models.CharField(verbose_name=u'Заголовок', max_length=256) + url = models.CharField(verbose_name=u'URL or URL RegEx', max_length=2048) + regex = models.BooleanField(verbose_name=u'RegEx', default=False) + sites = models.ManyToManyField(Site, related_name='site_urls', verbose_name=_('Sites'), null=True, blank=True) + + public = models.BooleanField(verbose_name=u'Активный', default=True) + created_at = models.DateTimeField(verbose_name=_('Created At'), auto_now_add=True) + updated_at = models.DateTimeField(verbose_name=_('Updated At'), auto_now=True) + + objects = models.Manager() + cached = URLCached() + + def __unicode__(self): + return self.title + + class Meta: + ordering = ['-created_at'] + verbose_name = _('URL') + verbose_name_plural = _('URLs') + + def get_admin_url(self): + return '/admin/expobanners/banners/url/%d/edit/'%self.id + + +class BannerGroup (models.Model): + name = models.CharField(verbose_name=u'Имя', max_length=255) + slug = models.SlugField(verbose_name=u'URL', unique=True) + width = models.PositiveSmallIntegerField(verbose_name=u'Ширина', default=0) + height = models.PositiveSmallIntegerField(verbose_name=u'Высота', default=0) + speed = models.PositiveSmallIntegerField(verbose_name=u'Скорость отображения', default=2000) + + public = models.BooleanField(verbose_name=u'Активная', default=True) + created_at = models.DateTimeField(verbose_name=_('Created At'), auto_now_add=True) + updated_at = models.DateTimeField(verbose_name=_('Updated At'), auto_now=True) + + objects = models.Manager() + cached = BannerGroupCached() + + def size(self): + return '%sx%s' % (self.width, self.height) + + def __unicode__(self): + return '%s - [%s x %s]' % (self.name, self.width, self.height) + + class Meta: + ordering = ['name'] + verbose_name = _('Banner Group') + verbose_name_plural = _('Banner Groups') + + def get_admin_url(self): + return '/admin/expobanners/banners/group/%d/edit/'%self.id + + +class Banner(models.Model, StatMixin): + objects = BiasedManager() + + title = models.CharField(verbose_name=u'Заголовок', max_length=255, blank=True) + alt = models.CharField(verbose_name=_('Alt'), max_length=255) + + text = models.TextField(verbose_name=u'Текст', blank=True, null=True) + img = models.FileField(verbose_name=u'Картинка', upload_to='expo_upload', blank=True, null=True) + url = models.CharField(verbose_name=u'URL', max_length=1024) + fr = models.DateField(default=date.today()) + to = models.DateField(blank=True, null=True) + + theme = models.ManyToManyField(Theme, blank=True, null=True, verbose_name=u'Тематика') + country = models.ManyToManyField(Country, blank=True, null=True, verbose_name=u'Страна') + + sort = models.PositiveSmallIntegerField(verbose_name=u'Сорт', default=500) + + group = models.ForeignKey(BannerGroup, related_name='banners', verbose_name=u'Место', null=True, blank=True) + often = models.PositiveSmallIntegerField( + verbose_name=_('Often'), + help_text=_('A ten will display 10 times more often that a one.'), + choices=[[i, i] for i in range(11)], + default=1 + ) + urls = models.ManyToManyField(URL, related_name='url_banners', verbose_name=_('URLs'), null=True, blank=True) + + html = models.BooleanField(verbose_name=_('HTML?'), default=False) + flash = models.BooleanField(verbose_name=_('Flash?'), default=False) + js = models.BooleanField(verbose_name=_('Javascript?'), default=False) + paid = models.BooleanField(verbose_name=_('Is Paid event link?'), default=False) + + public = models.BooleanField(verbose_name=u'Активный', default=True) + created_at = models.DateTimeField(verbose_name=_('Created At'), auto_now_add=True) + updated_at = models.DateTimeField(verbose_name=_('Updated At'), auto_now=True) + + stat_pswd = models.CharField(max_length=16) + + class Meta: + ordering = ['-public'] + + def get_admin_url(self): + return '/admin/expobanners/banners/banner/%d/edit/'%self.id + + + def key(slef): + if hasattr(settings, 'SECRET_KEY'): + key = str(datetime.now()) + settings.SECRET_KEY + else: + key = str(datetime.now()) + return hashlib.md5(key).hexdigest() + + def log(self, request, type): + log = { + 'type': type, + 'banner': self, + 'group': self.group, + 'ip': request.META.get('REMOTE_ADDR'), + 'user_agent': request.META.get('HTTP_USER_AGENT'), + 'page': request.META.get('HTTP_REFERER'), + } + + if request.user.is_authenticated(): + log['user'] = request.user + return Log.objects.create(**log) + + @models.permalink + def image(self): + return ('banner_view', (), {'banner_id': self.pk, 'key': self.key()}) + + def impressions(self): + return Log.objects.filter(banner=self.pk, type=0).count() + + def views(self): + return Log.objects.filter(banner=self.pk, type=1).count() + + def clicks(self): + return Log.objects.filter(banner=self.pk, type=2).count() + + def __unicode__(self): + return self.title or self.alt + + def get_absolute_url(self): + if self.url == '#': + return self.url + else: + @models.permalink + def get_absolute_url(self): + return ('banner_click', (), {'banner_id': self.pk, 'key': self.key()}) + return get_absolute_url(self) + + def get_click_link(self): + return '/expo-b/click/%d/'%self.id + + class Meta: + ordering = ['sort'] + verbose_name = _('Banner') + verbose_name_plural = _('Banners') + + +class Log(models.Model): + banner = models.ForeignKey(Banner, related_name='banner_logs') + group = models.ForeignKey(BannerGroup, related_name='group_logs', verbose_name=_('Group'), blank=True, null=True) + urls = models.ManyToManyField(URL, related_name='url_logs', verbose_name=_('URLs'), blank=True) + + user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='users', verbose_name=_('User')) + datetime = models.DateTimeField(verbose_name=_('Clicked At'), auto_now_add=True) + ip = models.IPAddressField(verbose_name=_('IP'), null=True, blank=True) + user_agent = models.CharField(verbose_name=_('User Agent'), max_length=1024, null=True, blank=True) + page = models.URLField(verbose_name=_('Page'), null=True, blank=True) + key = models.CharField(verbose_name=_('User Agent'), max_length=32, null=True, blank=True) + TYPE_CHOICES = ( + (0, 'impressions'), + (1, 'view'), + (2, 'click') + ) + + type = models.PositiveSmallIntegerField(verbose_name=_('Type'), max_length=1, default=0, choices=TYPE_CHOICES) + + def __unicode__(self): + return '%s - (%s)' % (self.banner, self.datetime) + + +class LogStat(models.Model): + banner = models.ForeignKey(Banner, related_name='banner_stat', verbose_name=_('Banner'), blank=True) + group = models.ForeignKey(BannerGroup, related_name='group_stat', verbose_name=_('Group'), blank=True, null=True) + urls = models.ManyToManyField(URL, related_name='url_bloks', verbose_name=_('URLs'), null=True, blank=True) + + date = models.DateField(verbose_name=_('Data')) + view = models.PositiveIntegerField(verbose_name=_('Views')) + click = models.PositiveIntegerField(verbose_name=_('Clicks')) + unique_click = models.PositiveIntegerField(verbose_name=_('Unique Views'), blank=True, null=True) + unique_view = models.PositiveIntegerField(verbose_name=_('Unique Clicks')) + + def __unicode__(self): + return '%s - (%s)' % (self.banner, self.date) + + +# ------------------ +class Paid(models.Model, StatMixin): + tickets = models.ForeignKey(Banner, related_name='paid_tickets') + participation = models.ForeignKey(Banner, related_name='paid_participation') + official = models.ForeignKey(Banner, related_name='paid_official') + catalog = models.ForeignKey(Banner, related_name='paid_catalog') + logo = models.ImageField(upload_to='expo-b/paid', blank=True) + organiser = models.CharField(max_length=100, blank=True) + public = models.BooleanField(default=True, verbose_name=u'Активная') + stat_pswd = models.CharField(max_length=16) + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ['-public'] + + def get_event(self): + if self.exposition_set.all().exists(): + return self.exposition_set.all()[0] + return None + + +class PaidStat(models.Model): + paid = models.ForeignKey(Paid) + date = models.DateField(verbose_name=_('Date')) + page_views = models.PositiveIntegerField(default=0) + price_views = models.PositiveIntegerField(default=0) + catalog_views = models.PositiveIntegerField(default=0) + catalog_clicks = models.PositiveIntegerField(default=0) + tickets_clicks = models.PositiveIntegerField(default=0) + participation_clicks = models.PositiveIntegerField(default=0) + official_clicks = models.PositiveIntegerField(default=0) + + +class Top(models.Model, StatMixin): + catalog = models.CharField(max_length=16, verbose_name=u'Каталог для топа') + position = models.PositiveIntegerField(blank=True, default=2, null=True, verbose_name=u'Позиция') + theme = models.ManyToManyField('theme.Theme', blank=True, null=True, verbose_name=u'Тематики') + excluded_tags = models.ManyToManyField('theme.Tag', blank=True, null=True, verbose_name=u'Исключить теги') + country = models.ManyToManyField('country.Country', blank=True, null=True, verbose_name=u'Страны') + excluded_cities = models.ManyToManyField('city.City', blank=True, null=True, verbose_name=u'Исключить города') + fr = models.DateField(default=date.today(), verbose_name=u'Начало') + to = models.DateField(blank=True, null=True, verbose_name=u'Конец') + stat_pswd = models.CharField(max_length=16) + + objects = models.Manager() + cached = TopCached() + + class Meta: + ordering = ['position'] + + +class TopStat(models.Model): + date = models.DateField() + theme = models.ForeignKey('theme.Theme', blank=True, null=True) + tag = models.ForeignKey('theme.Tag', blank=True, null=True) + country = models.ForeignKey('country.Country', blank=True, null=True) + city = models.ForeignKey('city.City', blank=True, null=True) + views = models.PositiveIntegerField(default=0) + clicks = models.PositiveIntegerField(default=0) + +def generatePassword(length=5): + """ + generate random password + """ + SYMBOLS = [',', '.', '?', '!', '-', '+', '1', '2', '3', '4', '5', '6', '7', '8', + '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', + 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#'] + PASSWORD_LENGTH = length + newPassword = [] + for i in range(PASSWORD_LENGTH): + newPassword.append(SYMBOLS[random.randrange(0, len(SYMBOLS))]) + return ''.join(newPassword) + + +def generate_stat_pass(sender, **kwargs): + obj = kwargs['instance'] + if not obj.stat_pswd: + obj.stat_pswd = generatePassword() + obj.save() + + + +post_save.connect(generate_stat_pass, sender=Banner) +post_save.connect(generate_stat_pass, sender=Paid) +post_save.connect(generate_stat_pass, sender=Top) diff --git a/expobanner/stat_views.py b/expobanner/stat_views.py new file mode 100644 index 00000000..a1ac7ae7 --- /dev/null +++ b/expobanner/stat_views.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from django.http import HttpResponseRedirect +from django.views.generic import TemplateView, CreateView, ListView, UpdateView, DetailView +from django.views.generic.edit import FormMixin +from django.forms.util import ErrorList +from django.conf import settings +from django.core.urlresolvers import reverse +from expobanner.models import Banner, Paid +from expobanner.forms import ClientStatForm + + + +class BannerStat(FormMixin, DetailView): + model = Banner + form_class = ClientStatForm + template_name = 'client/expobanners/banner_stat.html' + + def get_success_url(self): + return reverse('banner_stat_client', kwargs={'pk': self.object.pk}) + + def get_context_data(self, **kwargs): + context = super(BannerStat, self).get_context_data(**kwargs) + obj = self.object + cookie_name = obj.get_cookie_name() + cookie = self.request.session.get(cookie_name) + if not cookie: + form = context.get('form') + # form in context if form invalid called + if not form: + context['form'] = self.get_form(self.form_class) + + return context + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + form = self.get_form(self.form_class) + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_valid(self, form): + obj = self.object + success = form.check_pass(obj) + if success: + self.request.session[obj.get_cookie_name()] = 1 + return HttpResponseRedirect(self.get_success_url()) + else: + form.errors['stat_pswd'] = ErrorList([u'Неправильный пароль']) + return self.form_invalid(form) + +class PaidStat(BannerStat): + model = Paid + template_name = 'client/expobanners/paid_stat.html' + + def get_success_url(self): + return reverse('paid_stat_client', kwargs={'pk': self.object.pk}) \ No newline at end of file diff --git a/expobanner/static/banners/css/slider.css b/expobanner/static/banners/css/slider.css new file mode 100644 index 00000000..d6d86672 --- /dev/null +++ b/expobanner/static/banners/css/slider.css @@ -0,0 +1,159 @@ +/* + ************************** + * =COMMON + ************************** +*/ +.b-slider { + position: relative; + display: block; + overflow: hidden; + margin: 0; + padding: 0; + width: 725px; + height: 360px; +} + +.b-slider-list { + position: absolute; + display: block; + overflow: hidden; + margin: 0; + padding: 0; + width: 725px; + height: 360px; + list-style: none; +} + +.b-slider-item { + position: absolute; + z-index: 50; + float: left; + overflow: hidden; + margin: 0; + padding: 0; + width: 725px; + height: 360px; +} + +.m-slider-current { + z-index: 100; +} + +.b-slider-item__img, +.b-slider-item__title, +.b-slider-item__text { + position: absolute; +} + +.b-slider-item__img { + top: 0; + left: 0; + text-decoration: none; +} + +.b-slider-item__link { + +} + +.b-slider-item__title { + bottom: 10px; + left: 10px; + width: 500px; + color: #333; + text-transform: uppercase; + text-shadow: 0 0 3px #fff, 0 0 2px #fff, 0 0 1px #fff; + letter-spacing: -5px; + font-size: 65px; + font-family: Calibri; + line-height: 0.8em; +} + +.b-slider-item__text { + position: absolute; + top: 150px; + left: 300px; + display: inline-block; + display: none; + margin: -10px; + padding: 10px; + max-width: 300px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.5); + box-shadow: 0 0 5px #fff; + color: #555; + line-height: 1.5em; +} + + +/* + ************************** + * =NAV + ************************** +*/ +.b-slider-nav { + position: absolute; + width: 100%; + top: 10px; + height: 30px; + left: 0; + z-index: 500; + display: block; + margin: 0; + padding: 0; + list-style-type: none; + text-align: center; +} + +.b-slider-nav-button { + display: inline-block; + margin: 5px; + width: 16px; + height: 16px; + border-radius: 8px; + background: #ddd; + color: transparent; + text-align: center; + font-weight: bold; + font-size: 8px; + line-height: 16px; + cursor: pointer; +} + +.b-slider-nav-button:hover { + background: #eee; +} + +.m-slider-nav-current_button { + background: #fff; +} + +.b-slider-nav-prev, +.b-slider-nav-next { + position: absolute; + top: 0; + z-index: 500; + padding-right: 20px; + padding-left: 20px; + color: #000; + text-shadow: 0 0 3px #fff, 0 0 2px #fff, 0 0 1px #fff; + font-size: 50px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + line-height: 360px; + opacity: 0.5; + cursor: pointer; +} + +.b-slider-nav-prev:hover, +.b-slider-nav-next:hover { + opacity: 0.8; +} + +.b-slider-nav-prev { + left: 0; + padding-left: 0; +} + +.b-slider-nav-next { + right: 0; + padding-right: 0; +} diff --git a/expobanner/static/banners/js/log.js b/expobanner/static/banners/js/log.js new file mode 100644 index 00000000..eed8e2a1 --- /dev/null +++ b/expobanner/static/banners/js/log.js @@ -0,0 +1,10 @@ + +$(document).ready(function() { + $('.b-banner-click').on('click', function(){ + $.get($(this).data('url')); + }); + + $(".b-banner").on('load', function() { + $.get($(this).data('view')); + }); +}); diff --git a/expobanner/static/banners/js/slider.js b/expobanner/static/banners/js/slider.js new file mode 100644 index 00000000..34769495 --- /dev/null +++ b/expobanner/static/banners/js/slider.js @@ -0,0 +1,71 @@ +$.fn.bannersSlider = function(options) { + $this = this; + var settings = $.extend( { + 'auto_play': false, + 'effect': 'fade', + 'speed' : 3000 + }, options); + + $this.current = 1; + $this.old = 1; + $this.len = $this.find('.b-slider-list li').length; + + $this.find('.b-slider-item').hide(); + $this.find('.m-slider-item-1').show(); + + $this.fadeTo = function(new_num) { + old_num = $this.old; + if (new_num != old_num) { + $this.find('.m-slider-item-' + new_num).hide(); + $this.find('.m-slider-item-' + new_num).fadeIn(1000); + $this.find('.m-slider-current').fadeOut(1000); + $this.find('.m-slider-current').removeClass('m-slider-current'); + $this.find('.m-slider-item-' + new_num).addClass('m-slider-current'); + + $this.find('.b-slider-nav-button').removeClass('m-slider-nav-current_button'); + + $this.find('.b-slider-nav-button[data-slide=' + new_num + ']').addClass('m-slider-nav-current_button'); + } + }; + + $this.prev = function() { + prev = ($this.current != 1) ? $this.current - 1 : $this.len; + $this.old = $this.current; + $this.current = prev; + + $this.fadeTo(prev); + }; + + $this.next = function() { + next = ($this.current != $this.len) ? $this.current + 1 : 1; + $this.old = $this.current; + $this.current = next; + + $this.fadeTo(next); + }; + + $this.children('.b-slider-nav-prev').on('click', function(){ + $this.prev(); + }); + + $this.children('.b-slider-nav-next').on('click', function(){ + $this.next(); + }); + + $this.find('.b-slider-nav-button').on('click', function(){ + to_slide = $(this).data('slide'); + + $this.old = $this.current; + $this.current = to_slide; + + $this.fadeTo(to_slide); + }); + + if (settings.auto_play) { + setInterval(function() { + $this.next(); + }, settings.speed); + } + + return $this; +}; diff --git a/expobanner/templates/banner.html b/expobanner/templates/banner.html new file mode 100644 index 00000000..85f460e1 --- /dev/null +++ b/expobanner/templates/banner.html @@ -0,0 +1,55 @@ +{% if banner.html %} + {% load banner %} + +{% elif banner.flash %} + + + +{% else %} + +{% endif %} + + \ No newline at end of file diff --git a/expobanner/templates/grid.html b/expobanner/templates/grid.html new file mode 100644 index 00000000..9571fd2d --- /dev/null +++ b/expobanner/templates/grid.html @@ -0,0 +1,32 @@ +{% load banner %} + +
+ {% for banner in banners %} + {% banner_one banner.id %} + {% endfor %} +
+ + \ No newline at end of file diff --git a/expobanner/templates/group.html b/expobanner/templates/group.html new file mode 100644 index 00000000..36800ccc --- /dev/null +++ b/expobanner/templates/group.html @@ -0,0 +1,30 @@ +{% load banner %} + +
+ {% for banner in banners %} + {% banner_one banner.id %} + {% endfor %} + +
\ No newline at end of file diff --git a/expobanner/templates/slider.html b/expobanner/templates/slider.html new file mode 100644 index 00000000..fcd6bd12 --- /dev/null +++ b/expobanner/templates/slider.html @@ -0,0 +1,74 @@ +{% load thumbnail %} + +{% if banners %} + +
+ + +
    + {% for banner in banners %} +
  • + {{ forloop.counter }} +
  • + {% endfor %} +
+ + 〈   +   〉 + +
+ + + + + +{% endif %} \ No newline at end of file diff --git a/expobanner/templatetags/__init__.py b/expobanner/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/expobanner/templatetags/banner.py b/expobanner/templatetags/banner.py new file mode 100644 index 00000000..c1491a74 --- /dev/null +++ b/expobanner/templatetags/banner.py @@ -0,0 +1,74 @@ +from ..models import Banner +from ..models import BannerGroup +from ..models import URL + +from django import template + +# For render tag +from django.template import Context +from django.template import Template + +import re + +register = template.Library() + + +@register.simple_tag(takes_context=True) +def banner_group(context, group, tpl='group.html'): + try: + page_url = context['request'].path_info + site = context['request'].site + group = BannerGroup.objects.get(slug=group) + good_urls = [] + for url in URL.objects.filter(public=True, sites__in=[site]): + if url.regex: + url_re = re.compile(url.url) + if url_re.findall(page_url): + good_urls.append(url) + elif page_url == url.url: + good_urls.append(url) + banners = Banner.objects.filter(public=True, group=group, urls__in=good_urls) + except: + banners = False + group = False + if(banners and group): + context['banners'] = banners + context['group'] = group + + t = template.loader.get_template(tpl) + return t.render(template.Context(context)) + + +@register.simple_tag(takes_context=True) +def banner_one(context, banner_id, tpl='banner.html'): + try: + page_url = context['request'].path_info + site = context['request'].site + good_urls = [] + for url in URL.objects.filter(public=True, sites__in=[site]): + if url.regex: + url_re = re.compile(url.url) + if url_re.findall(page_url): + good_urls.append(url) + elif page_url == url.url: + good_urls.append(url) + + banner = Banner.objects.get(id=banner_id, public=True, urls__in=good_urls) + except: + banner = False + + context['banner'] = banner + + t = template.loader.get_template(tpl) + return t.render(template.Context(context)) + + +# block render +@register.simple_tag(takes_context=True) +def render(context, content): + try: + tpl = Template(content) + content = Context(context) + return tpl.render(content) + except: + return 'Render Error' diff --git a/expobanner/tests.py b/expobanner/tests.py new file mode 100644 index 00000000..501deb77 --- /dev/null +++ b/expobanner/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/expobanner/urls.py b/expobanner/urls.py new file mode 100644 index 00000000..189b2023 --- /dev/null +++ b/expobanner/urls.py @@ -0,0 +1,13 @@ +from django.conf.urls import url +from expobanner.stat_views import * +from . import views + +urlpatterns = [ + url(r'^click/(?P\d{1,4})/$', views.click, name='banner_click'), + #url(r'^view/(?P\d+)/$', views.view, name='banner_view'), + # + url(r'^get-banners/$', views.get_banners), + url(r'^get-tops/$', views.get_top), + url(r'^banner/(?P\d+)/stat/$', BannerStat.as_view(), name='banner_stat_client'), + url(r'^paid/(?P\d+)/stat/$', PaidStat.as_view(), name='paid_stat_client'), +] diff --git a/expobanner/utils.py b/expobanner/utils.py new file mode 100644 index 00000000..a7a9cd53 --- /dev/null +++ b/expobanner/utils.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import random +from django.db import connection + +def get_client_ip(request): + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[0] + else: + ip = request.META.get('REMOTE_ADDR') + return ip + +def get_by_sort(banner_list): + max_sort = 0 + for banner in banner_list: + sort = banner.sort + if sort > max_sort: + max_sort = sort + result = [banner for banner in banner_list if banner.sort == max_sort] + return result + + +def get_banner_by_params(banners_list, urls, params): + #print('START. NUMBER of queries = %d'%len(connection.queries)) + thematic_banners = [] + url_banners = [] + + for banner in banners_list: + #print('-------------------------') + #print('number of queries = %d'%len(connection.queries)) + + # check by theme + banner_theme_ids = [str(theme.id) for theme in banner.theme.all()] + #print('number of queries = %d'%len(connection.queries)) + + if banner_theme_ids: + if params.get('theme'): + theme = params['theme'] + if theme in banner_theme_ids: + thematic_banners.append(banner) + continue + # check by country + banner_country_ids = [str(country.id) for country in banner.country.all()] + #print('number of queries = %d'%len(connection.queries)) + if banner_country_ids: + if params.get('country'): + + country = params['country'] + if country in banner_country_ids: + thematic_banners.append(banner) + continue + + # check by url + if urls: + banner_urls = banner.urls.all() + print('number of queries = %d'%len(connection.queries)) + + if banner_urls: + + banner_urls = set(banner_urls) + common_urls = set(urls).intersection(banner_urls) + + if common_urls: + url_banners.append(banner) + continue + print('-------------------------') + if thematic_banners: + return random.choice(thematic_banners) + if url_banners: + return random.choice(url_banners) + return None + + #print('END. NUMBER of queries = %d'%len(connection.queries)) diff --git a/expobanner/views.py b/expobanner/views.py new file mode 100644 index 00000000..d0e38527 --- /dev/null +++ b/expobanner/views.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +import json +import re +from django.http import HttpResponse +from django.shortcuts import redirect, get_object_or_404 +from .models import Banner, BannerGroup, URL, Top +from expobanner.utils import get_by_sort, get_banner_by_params, get_client_ip + + +def click(request, banner_id): + banner = get_object_or_404(Banner, pk=banner_id) + banner.log(request, 2) + return redirect(banner.url) + + +def view(request, banner_id): + banner = get_object_or_404(Banner, pk=banner_id) + banner.log(request, 1) + return redirect(banner.img.url) + +def get_banners(request): + #url = request.GET.get('url', '/') + url = request.META.get('HTTP_REFERER', '/') + # get urls by current url + urls = URL.cached.all() + good_urls = [] + for u in urls: + if u.regex: + url_re = re.compile(u.url) + if url_re.findall(url): + good_urls.append(u) + elif url == u.url: + good_urls.append(u) + # fill parameters dict + params = {'theme': request.GET.get('theme'), + 'tag': request.GET.get('tag'), + 'country': request.GET.get('country'), + 'city': request.GET.get('city'), + 'ip': get_client_ip(request)} + + group_banners = BannerGroup.cached.group_banners() + result = [] + # get banners for all groups + for group, banners in group_banners.iteritems(): + banner = get_banner_by_params(banners, good_urls, params) + if banner: + if banner.js or banner.html: + text = banner.text + img = '' + alt = '' + is_img = False + else: + text = '' + img = banner.img.url + alt = banner.alt + is_img = True + result.append({'id': group, + 'url': banner.get_click_link(), + 'is_html': banner.html, + 'is_flash': banner.flash, + 'is_img': is_img, + 'is_js': banner.js, + 'img': img, + 'alt': alt, + 'text': text + }) + # add view log + banner.log(request, 1) + + return HttpResponse(json.dumps(result, indent=4), content_type='application/json') + +def get_top_events(tops, params): + catalog = params.get('catalog') + country = params.get('country', '') + theme = params.get('theme', []) + good_tops = [] + for top in tops: + + if top.catalog != catalog: + continue + country_ids = [str(item.id) for item in top.country.all()] + if not country in country_ids: + continue + + +from exposition.models import Exposition +from django.shortcuts import render_to_response +from django.template import RequestContext +def get_top(request): + params = {'theme': request.GET.get('theme'), + 'tag': request.GET.get('tag'), + 'country': request.GET.get('country'), + 'city': request.GET.get('city'), + 'catalog': request.GET.get('catalog')} + + tops = Top.cached.all() + events = get_top_events(tops, params) + expos = Exposition.objects.filter(top__isnull=False) + context = {'objects': expos} + return render_to_response('client/includes/exposition/expo_top.html', context, context_instance=RequestContext(request)) \ No newline at end of file diff --git a/exposition/models.py b/exposition/models.py index dce42537..76b7177f 100644 --- a/exposition/models.py +++ b/exposition/models.py @@ -155,6 +155,9 @@ class Exposition(TranslatableModel, EventMixin, ExpoMixin): max_open_area = models.PositiveIntegerField(verbose_name='Максимальная цена открытой площади', blank=True, null=True) registration_payment = models.PositiveIntegerField(verbose_name='Регистрационный взнос', blank=True, null=True) + + paid_new = models.ForeignKey('expobanner.Paid', blank=True, null=True, on_delete=models.SET_NULL) + top = models.ForeignKey('expobanner.Top', blank=True, null=True, on_delete=models.SET_NULL) #set manager of this model(fisrt manager is default) objects = ExpoManager() enable = ClientManager() diff --git a/exposition/search_indexes.py b/exposition/search_indexes.py index 639a9c7e..0a427eb7 100644 --- a/exposition/search_indexes.py +++ b/exposition/search_indexes.py @@ -19,9 +19,9 @@ class ExpositionIndex(indexes.SearchIndex, indexes.Indexable, ExpoSearchMixin): content_auto = indexes.EdgeNgramField() form_name = indexes.CharField() # translated fields - name_en = indexes.CharField() + name_en = indexes.CharField() name_ru = indexes.CharField() - catalog_name_en = indexes.CharField() + catalog_name_en = indexes.CharField() catalog_name_ru = indexes.CharField() def prepare_form_name(self, obj): diff --git a/exposition/views.py b/exposition/views.py index 48b55b3b..b6464599 100644 --- a/exposition/views.py +++ b/exposition/views.py @@ -151,6 +151,7 @@ class ExpoDetail(JitterCacheMixin, MetadataMixin, DetailView): model = Exposition slug_field = 'url' template_name = 'client/exposition/exposition_detail.html' + queryset = Exposition.objects.language().select_related('place', 'city', 'country', 'paid_new') def get_context_data(self, **kwargs): context = super(ExpoDetail, self).get_context_data(**kwargs) @@ -167,6 +168,7 @@ class ExpositionPrice(MetadataMixin, DetailView): model = Exposition slug_field = 'url' template_name = 'client/exposition/price.html' + queryset = Exposition.objects.language().select_related('place', 'city', 'country', 'paid_new') @@ -375,6 +377,11 @@ class ExpoCountryCatalog(ExpoCatalog): qs = self.model.enable.upcoming().filter(country=country) return qs + def get_context_data(self, **kwargs): + context = super(ExpoCountryCatalog, self).get_context_data(**kwargs) + context['country'] = str(self.kwargs['country'].id) + return context + class ExpoCityCatalog(ExpoCatalog): catalog_url = '/expo/city/' def get_filtered_qs(self): @@ -385,6 +392,10 @@ class ExpoCityCatalog(ExpoCatalog): qs = self.model.enable.upcoming().filter(city=city) self.filter_object = city return qs + def get_context_data(self, **kwargs): + context = super(ExpoCityCatalog, self).get_context_data(**kwargs) + context['city'] = str(self.kwargs['city'].id) + return context class ExpoThemeCatalog(ExpoCatalog): @@ -423,10 +434,11 @@ class ExpoThemeCatalog(ExpoCatalog): def get_context_data(self, **kwargs): context = super(ExpoThemeCatalog, self).get_context_data(**kwargs) if self.country: - context['country'] = self.country + context['country'] = str(self.country.id) if self.city: - context['city'] = self.city + context['city'] = str(self.city.id) context['theme_for_filter'] = self.kwargs['theme'] + context['themes'] = [str(self.kwargs['theme'].id)] return context @@ -449,6 +461,8 @@ class ExpoTagCatalog(ExpoCatalog): context = super(ExpoTagCatalog, self).get_context_data(**kwargs) tag = self.kwargs['tag'] context['theme_for_filter'] = tag.theme + context['themes'] = [str(tag.theme.id)] + context['tag'] = str(self.kwargs['tag'].id) return context diff --git a/functions/model_mixin.py b/functions/model_mixin.py index a96c53cc..5577ac9c 100644 --- a/functions/model_mixin.py +++ b/functions/model_mixin.py @@ -34,6 +34,9 @@ class EventMixin(object): url = '%s%s/'%(self.get_catalog_url(), self.url) return url + def get_paid_catalog_url(self): + return self.paid_new.catalog.get_click_link() + def org_split(self): if self.org: return self.org.split(';') diff --git a/functions/models_methods.py b/functions/models_methods.py index 486fb71e..35121ea9 100644 --- a/functions/models_methods.py +++ b/functions/models_methods.py @@ -11,13 +11,13 @@ class ExpoManager(TranslationManager): def upcoming(self): return self.language().select_related('country', 'city', 'place').filter(data_begin__gte=datetime.datetime.now().date()).order_by('data_begin') - + """ def all(self, lang=None): if lang: return super(ExpoManager, self).language(lang).all().order_by('name') else: return super(ExpoManager, self).language(get_language()).all().order_by('name') - + """ def safe_get(self, **kwargs): model = self.model try: diff --git a/functions/pipeline.py b/functions/pipeline.py index 1d167830..d7260b00 100644 --- a/functions/pipeline.py +++ b/functions/pipeline.py @@ -1,41 +1,97 @@ from accounts.models import User +import random +import string +def random_pass(): + digits = random.sample(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), 4) + chars = random.sample(string.lowercase[:], 4) + password = chars + digits + random.shuffle(password) + return ''.join(password) -def load_user(details, response, uid, *args, **kwargs): - if details.get('email'): - username = details.get('email') - else: - username = str(uid) - user = User.objects.safe_get(username=username) +def load_user(strategy, details, response, uid, *args, **kwargs): + user = None + if details.get('email'): + email = details.get('email') + user = User.objects.safe_get(email=email) return {'user': user, 'is_new': False} +from django.contrib.sites.models import Site, RequestSite +from registration.models import RegistrationProfile + def create_user(strategy, details, response, uid, user=None, *args, **kwargs): if user: return {'user': user, 'is_new': False} else: + request = strategy.request + if Site._meta.installed: + site = Site.objects.get_current() + else: + site = RequestSite(request) + new_user = RegistrationProfile.objects.create_inactive_user(details['first_name'], details['last_name'], details['email'], + random_pass(), site or 1) + signals.user_registered.send(sender=User, user=new_user, request=request) + #user = User.objects.create_social_user(username, details['first_name'], details['last_name']) + return {'user': new_user, 'is_new': True} - if details.get('email'): - username = details.get('email') + +from django.shortcuts import redirect +from social.pipeline.partial import partial +from registration import signals + +@partial +def require_email(strategy, details, user=None, is_new=False, *args, **kwargs): + if user and user.email: + return + elif is_new and not details.get('email'): + email = strategy.request_data().get('email') + if email: + details['email'] = email else: - username = str(uid) + strategy.request.session['new_email'] = True + return redirect('acquire_email') + + + - user = User.objects.create_social_user(username, details['first_name'], details['last_name']) - return {'user': user, 'is_new': True} + + +from django.core import signing +from django.core.mail import EmailMultiAlternatives +from django.conf import settings +from django.core.urlresolvers import reverse + +def SendVerificationEmail(strategy, backend, code): """ - if details.get('email'): - user = User.objects.safe_get(email=details['email']) - if user: - return {'user': user, 'is_new': False} - else: - user = User.objects.create_user(email=details['email'], first_name=details['first_name'], - last_name=details['last_name'], password='1q2w3e4r', is_active=True) + Send an email with an embedded verification code and the necessary details to restore the required session + elements to complete the verification and sign-in, regardless of what browser the user completes the + verification from. + """ + signature = signing.dumps({"session_key": strategy.session.session_key, "email": code.email}, + key=settings.EMAIL_SECRET_KEY) + verifyURL = "{0}?verification_code={1}&signature={2}".format( + reverse('social:complete', args=(backend.name,)), + code.code, signature) + verifyURL = strategy.request.build_absolute_uri(verifyURL) - return {'user': user, 'is_new': True} - else: - return None - """ \ No newline at end of file + emailHTML = ''# Include your function that returns an html string here + emailText = """Welcome to Expomap.ru! +In order to login with your new user account, you need to verify your email address with us. +Please click on this link to continue registration. +""".format(verifyURL=verifyURL) + + kwargs = { + "subject": "Verify Your Account", + "body": emailText, + "from_email": settings.CALLBACK_EMAIL, + "to": [code.email], + } + + email = EmailMultiAlternatives(**kwargs) + email.attach_alternative(emailHTML, "text/html") + email.send() \ No newline at end of file diff --git a/functions/search_forms.py b/functions/search_forms.py index f65fc334..557f356f 100644 --- a/functions/search_forms.py +++ b/functions/search_forms.py @@ -239,7 +239,6 @@ class ExpositionSearchForm(AbstactSearchForm): required=False, widget=forms.CheckboxSelectMultiple()) tg = forms.CharField(label=_(u'Теги'), required=False, widget=forms.CheckboxSelectMultiple()) #co = forms.CharField(label=_(u'Страна'), required=False, widget=forms.CheckboxSelectMultiple()) - #tg = forms.CharField(label=_(u'Теги'), required=False, widget=forms.CheckboxSelectMultiple()) area = forms.MultipleChoiceField(label=_(u'Регион'), choices=[(item.id, item.name) for item in Area.objects.language().all()], diff --git a/import_xls/utils.py b/import_xls/utils.py index 8efe455c..018bc924 100644 --- a/import_xls/utils.py +++ b/import_xls/utils.py @@ -65,12 +65,12 @@ def to_country(value): return None def to_city(value, lang, country): - try: # get city by name #objects = get_translation_aware_manager(City) # except IndexError if no found city = City.objects.filter(translations__name=value, country=country)[0] + # print(city) return city.id except IndexError: print('---------city error------------') @@ -173,7 +173,6 @@ def save_logo(obj, path): full_path = settings.MEDIA_ROOT + logo_path - try: alt_name = get_alternative_filename(full_path, file_name) except UnicodeEncodeError: diff --git a/meta/admin_urls.py b/meta/admin_urls.py index f2062f38..a545ff76 100644 --- a/meta/admin_urls.py +++ b/meta/admin_urls.py @@ -1,9 +1,14 @@ # -*- coding: utf-8 -*- from django.conf.urls import patterns, include, url from admin import MetaListView, MetaView +from .views import CreateSeoText, SeoTextList, EditSeoText, DeleteSeoText urlpatterns = patterns('conference.admin', + url(r'^seo/new/$', CreateSeoText.as_view(), name='seo_new'), + url(r'^seo/all/$', SeoTextList.as_view(), name='seo_all'), + url(r'^seo/edit/(?P\d{1,5})/$', EditSeoText.as_view(), name='seo_edit'), + url(r'^seo/delete/(?P\d{1,5})/$', DeleteSeoText.as_view(), name='seo_delete'), url(r'^all/$', MetaListView.as_view()), #url(r'^change/(?P.*)/$', 'conference_change'), url(r'^(?P.*)/$', MetaView.as_view()), diff --git a/meta/forms.py b/meta/forms.py index ee8983cc..eb93f28a 100644 --- a/meta/forms.py +++ b/meta/forms.py @@ -45,4 +45,16 @@ class MetaForm(forms.Form): meta.save() class MetaFilterForm(AdminFilterForm): - model = MetaSetting \ No newline at end of file + model = MetaSetting + + +from .models import SeoText +from ckeditor.widgets import CKEditorWidget +from hvad.forms import TranslatableModelForm + +class SeoTextForm(TranslatableModelForm): + + class Meta: + model = SeoText + fields = ['url', 'title', 'body'] + widgets = {'body':CKEditorWidget} \ No newline at end of file diff --git a/meta/models.py b/meta/models.py index ede47701..0bd6bdc0 100644 --- a/meta/models.py +++ b/meta/models.py @@ -113,4 +113,67 @@ class MetaSetting(TranslatableModel): return [] -post_save.connect(post_save_handler, sender=MetaSetting) \ No newline at end of file +post_save.connect(post_save_handler, sender=MetaSetting) + + +# SEO - tests # + +from django.db import models +from hvad.models import TranslatableModel, TranslatedFields +from django.conf import settings +from django.core.urlresolvers import reverse_lazy +from django.core.cache import cache + + +class SeoTextManager(TranslationManager): + cache_time = 120 + def cache_get(self, *args, **kwargs): + url = kwargs.get('url') + lang = kwargs.get('lang')[:2] or translation.get_language()[:2] + key = 'seo_text_cache' + result = cache.get(key) + if result: + return result.get(lang+'_' + url) + + qs = list(SeoText.objects.language('all')) + value_dict = {obj.language_code+'_'+obj.url:obj for obj in qs} + cache.set(key, value_dict, self.cache_time) + return value_dict.get(lang+'_'+url) + + + +class SeoText(TranslatableModel): + + url = models.CharField(max_length=50, unique=True) + + translations = TranslatedFields( + title=models.CharField(max_length=255), + body=models.TextField() + ) + + objects = SeoTextManager() + + def get_absolute_url(self): + return reverse_lazy('seo_all') + + def save(self, *args, **kwargs): + super(SeoText,self).save(*args, **kwargs) + + all_field_names = list(self._translated_field_names) + clear_f_n = [] + for field_name in all_field_names: + if field_name not in ['master', 'master_id', u'id', 'language_code']: + clear_f_n.append(field_name) + field_items = {field_name:getattr(self, field_name) for field_name in clear_f_n} + + langs = [lan[0] for lan in settings.LANGUAGES] + for lang in langs: + if lang not in self.get_available_languages(): + self.translate(lang) + for field in clear_f_n: + setattr(self, field, field_items.get(field, '')) + super(SeoText,self).save(*args, **kwargs) + return SeoText + + def __unicode__(self): + return self.url diff --git a/meta/views.py b/meta/views.py index c8146091..28d30eba 100644 --- a/meta/views.py +++ b/meta/views.py @@ -187,4 +187,49 @@ class MetadataMixin(object): site_name=self.get_meta_site_name(context=context), ) - return context \ No newline at end of file + return context + +from django.views.generic import CreateView, UpdateView, DeleteView, ListView +from .models import SeoText +from .forms import SeoTextForm + + +class CreateSeoText(CreateView): + form_class = SeoTextForm + model = SeoText + template_name = "admin/meta/create_seo_text.html" + success_url = '/admin/meta/seo/all/' + + +class SeoTextList(ListView): + model = SeoText + template_name = "admin/meta/seo_admin_list.html" + + +class EditSeoText(UpdateView): + form_class = SeoTextForm + model = SeoText + template_name = "admin/meta/create_seo_text.html" + + +class DeleteSeoText(DeleteView): + model = SeoText + template_name = "admin/meta/seo_confirm_delete.html" + success_url = '/admin/meta/seo/all/' + + + + + + + + + + + + + + + + + diff --git a/proj/admin_urls.py b/proj/admin_urls.py index 0e66313d..47bbfeaf 100644 --- a/proj/admin_urls.py +++ b/proj/admin_urls.py @@ -12,12 +12,14 @@ urlpatterns = required( url(r'^$', AdminIndex.as_view()), url(r'^', include('import_xls.admin_urls')), url(r'^accounts/', include('accounts.admin_urls')), + url(r'page/', include('core.admin_urls')), url(r'^article/', include('article.admin_urls')), url(r'^photogallery/', include('photologue.admin_urls')), url(r'^city/', include('city.admin_urls')), url(r'^company/', include('company.admin_urls')), url(r'^conference/', include('conference.admin_urls')), url(r'^country/', include('country.admin_urls')), + url(r'^expobanners/', include('expobanner.admin_urls')), url(r'^exposition/', include('exposition.admin_urls')), url(r'^news/', include('news.admin_urls')), url(r'^organiser/', include('organiser.admin_urls')), diff --git a/proj/decorators.py b/proj/decorators.py new file mode 100644 index 00000000..f3f48c8d --- /dev/null +++ b/proj/decorators.py @@ -0,0 +1,16 @@ +from functools import wraps + +from django.template import RequestContext +from django.shortcuts import render_to_response + + +def render_to(tpl): + def decorator(func): + @wraps(func) + def wrapper(request, *args, **kwargs): + out = func(request, *args, **kwargs) + if isinstance(out, dict): + out = render_to_response(tpl, out, RequestContext(request)) + return out + return wrapper + return decorator \ No newline at end of file diff --git a/proj/settings.py b/proj/settings.py index 1268efa5..c2828869 100644 --- a/proj/settings.py +++ b/proj/settings.py @@ -42,7 +42,7 @@ CACHES = { # Hosts/domain names that are valid for this site; required if DEBUG is False # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ['hit.expomap.ru', '195.66.79.152', '195.66.79.145', 'expomap.ru'] +ALLOWED_HOSTS = ['hit.expomap.ru', '195.66.79.152', '195.66.79.145', 'expomap.ru', '195.66.79.148'] # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name @@ -108,7 +108,7 @@ STATIC_URL = '/static/' STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', + #'django.contrib.staticfiles.finders.DefaultStorageFinder', ) # Make this unique, and don't share it with anybody. @@ -173,6 +173,7 @@ TEMPLATE_DIRS = ( os.path.join(SITE_ROOT, 'templates/admin/organiser'), os.path.join(SITE_ROOT, 'templates/admin/place_conference'), os.path.join(SITE_ROOT, 'templates/admin/place_exposition'), + os.path.join(SITE_ROOT, 'templates/admin/page'), os.path.join(SITE_ROOT, 'templates/admin/photoreport'), os.path.join(SITE_ROOT, 'templates/admin/settings'), os.path.join(SITE_ROOT, 'templates/admin/seminar'), @@ -198,12 +199,13 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'localhost' EMAIL_HOST_USER = '' EMAIL_HOST_PASSWORD = '' -EMAIL_USE_TLS = False +EMAIL_USE_TLS = True EMAIL_PORT = 25 DEFAULT_FROM_EMAIL = "expomap.ru" AUTHENTICATION_BACKENDS = ( + 'social.backends.open_id.OpenIdAuth', 'social.backends.vk.VKOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.twitter.TwitterOAuth', @@ -216,14 +218,31 @@ AUTHENTICATION_BACKENDS = ( SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_USER_MODEL = 'accounts.User' -#SOCIAL_AUTH_UID_LENGTH = -#SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH = -#SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH = -#SOCIAL_AUTH_FORCE_EMAIL_VALIDATION = True +SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/logged-in/' +# The user will be redirected to this URL when a social account is disconnected +SOCIAL_AUTH_INACTIVE_USER_URL = '/inactive-user/' +# #Used to redirect the user once the auth process ended successfully. The value of ?next=/foo is used if it was present +# SOCIAL_AUTH_LOGIN_ERROR_URL = '/login-error/' +# #URL where the user will be redirected in case of an error +# SOCIAL_AUTH_LOGIN_URL = '/login-url/' +# #Is used as a fallback for LOGIN_ERROR_URL +# SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/new-users-redirect-url/' +# # Used to redirect new registered users, will be used in place of SOCIAL_AUTH_LOGIN_REDIRECT_URL if defined. +# Note that ?next=/foo is appended if present, if you want new users to go to next, you’ll need to do it yourself. +# SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = '/new-association-redirect-url/' +# # Like SOCIAL_AUTH_NEW_USER_REDIRECT_URL but for new associated accounts (user is already logged in). +# Used in place of SOCIAL_AUTH_LOGIN_REDIRECT_URL +# SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = '/account-disconnected-redirect-url/' + +# Inactive users can be redirected to this URL when trying to authenticate. +# SOCIAL_AUTH_UID_LENGTH = +# SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH = +# SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH = +# SOCIAL_AUTH_FORCE_EMAIL_VALIDATION = True SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = True -#SOCIAL_AUTH_STORAGE = 'social.apps.django_app.me.models.DjangoStorage' +from social.pipeline.social_auth import social_details SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', @@ -231,35 +250,53 @@ SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', + 'functions.pipeline.require_email', + #'social.pipeline.mail.mail_validation', 'functions.pipeline.create_user', #'social.pipeline.user.create_user', + 'social.pipeline.social_auth.associate_by_email', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) +REQUIRES_EMAIL_VALIDATION = True +SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = 'functions.pipeline.SendVerificationEmail' +SOCIAL_AUTH_EMAIL_VALIDATION_URL = '/email_verify_sent/' + + SOCIAL_AUTH_VK_OAUTH2_KEY = '3393841' SOCIAL_AUTH_VK_OAUTH2_SECRET = '2P19EBUEpLZifaabbREv' +SOCIAL_AUTH_VK_OAUTH2_SCOPE =['email'] +SOCIAL_AUTH_VK_OAUTH2_PROFILE_EXTRA_PARAMS = { + 'fields': 'email' +} SOCIAL_AUTH_FACEBOOK_KEY = '133775720059470' SOCIAL_AUTH_FACEBOOK_SECRET = '434edf89c24a290497646a739df656c6' +SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', 'publish_actions'] SOCIAL_AUTH_TWITTER_KEY = 'S6NX33FazTcWuqnXQhlOdg' SOCIAL_AUTH_TWITTER_SECRET = 'MxUGfySQmLI5kvqSoAtWsGje2eAHQL7Jo8mXuIZ4D0' +SOCIAL_AUTH_TWITTER_SCOPE = ['email'] SOCIAL_AUTH_GOOGLE_OAUTH_KEY = '1044044901114.apps.googleusercontent.com' SOCIAL_AUTH_GOOGLE_OAUTH_SECRET = 'j_McErlPPof88eNrmOXI-ZXI' +SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = ['email'] SOCIAL_AUTH_MAILRU_OAUTH2_KEY = '697945' SOCIAL_AUTH_MAILRU_OAUTH2_SECRET = '343581b9e31961b334532cc1880066e8' +SOCIAL_AUTH_MAILRU_OAUTH2_SCOPE = ['email'] SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_KEY = 'CBAQDCKIABABABABA' SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_SECRET = '814CDDCD3E2D2F278EF1591B' SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_PUBLIC_NAME = '128007936' +SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_PUBLIC_SCOPE = ['email'] SOCIAL_AUTH_LINKEDIN_KEY = 'jt9xwquj1fkd' SOCIAL_AUTH_LINKEDIN_SECRET = 'GvM2xQCNADaBfiMy' +SOCIAL_AUTH_LINKEDIN_SCOPE = ['email'] @@ -282,6 +319,7 @@ INSTALLED_APPS = ( 'core', 'country', 'directories', + 'expobanner', 'exposition', 'file', 'import_xls', @@ -314,8 +352,8 @@ INSTALLED_APPS = ( 'pytils', # ?? 'pymorphy', # ?? 'password_reset', # reset password - 'django_crontab', # crons 'social.apps.django_app.default', # social auth + 'core', ) @@ -363,15 +401,21 @@ LOGGING = { } } - -CRONJOBS = [ - ('0 */1 * * *', 'django.core.management.call_command', ['update_index conference --age=1']), - ('0 */1 * * *', 'django.core.management.call_command', ['update_index exposition --age=1']), - ('0 */12 * * *', 'django.core.management.call_command', ['update_index place_exposition --age=12']), - ('0 */24 * * *', 'django.core.management.call_command', ['update_index company --age=24']), - ('0 */24 * * *', 'django.core.management.call_command', ['update_index theme --age=24']), - ('0 */24 * * *', 'django.core.management.call_command', ['update_index tag --age=24']), -] +# TODO automate crons +""" +# update search indexes +0 * * * * /usr/bin/python /var/www/proj/manage.py update_index conference --remove --age=6 +0 * * * * /usr/bin/python /var/www/proj/manage.py update_index exposition --remove --age=6 +0 1,13 * * * /usr/bin/python /var/www/proj/manage.py update_index place_exposition --remove --age=24 +0 3 * * * /usr/bin/python /var/www/proj/manage.py update_index company --remove --age=48 +0 4 * * * /usr/bin/python /var/www/proj/manage.py update_index theme --remove --age=48 +0 5 * * * /usr/bin/python /var/www/proj/manage.py update_index tag --remove --age=48 +0 6 * * * /usr/bin/python /var/www/proj/manage.py update_index country --remove --age=48 +0 7 * * * /usr/bin/python /var/www/proj/manage.py update_index city --remove --age=48 +# update banner logs +10 * * * * /usr/bin/python /var/www/proj/manage.py banner_log_update +20 2,14 * * * /usr/bin/python /var/www/proj/manage.py banner_log_check_previous_day +""" THUMBNAIL_DEBUG = DEBUG diff --git a/proj/urls.py b/proj/urls.py index c73029c6..a8906dc1 100644 --- a/proj/urls.py +++ b/proj/urls.py @@ -43,6 +43,7 @@ urlpatterns = patterns('', url(r'^theme/', include('theme.urls')), url(r'^places/', include('place_exposition.urls')), url(r'^translators/', include('translator.urls')), + url(r'^expo-b/', include('expobanner.urls')), url(r'^', include('accounts.urls')), url(r'^', include('exposition.urls')), url(r'^', include('settings.conference_old_urls')), # conference redirects from old version diff --git a/proj/views.py b/proj/views.py index c5cb918d..0db0e5bd 100644 --- a/proj/views.py +++ b/proj/views.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.core.context_processors import csrf -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response from django.template import RequestContext from django.views.generic import TemplateView from django.conf import settings @@ -14,24 +14,53 @@ from functions.forms import ThemeSearch, PlaceSearch from functions.search_forms import EventSearchForm, ExpositionSearchForm from functions.custom_views import ExpoListView -from accounts.forms import RegistrationCompleteForm +from accounts.forms import RegistrationCompleteForm, SocialRegistrationCompleteForm +from meta.models import SeoText +from django.utils.translation import get_language + + +def clear_slashes(str_): + if str_[0] == r'/': + str_ = str_[1:] + if str_[-1] == r'/': + str_ = str_[:-1] + return str_ + +def add_seo(request): + url = request.path + lang = get_language() + try: + seo_text = SeoText.objects.cache_get(url=url, lang=lang) + except SeoText.DoesNotExist: + try: + seo_text = SeoText.objects.cache_get(url=url[:-1], lang=lang) + except SeoText.DoesNotExist: + seo_text = None + return seo_text def expo_context(request): banners_themes = [24, 34, 4] - banner_tags = [141, 142, 143, 156, 206, 231, 232, 390, 391, 400, 457, 500, 536, 537, 539, 457, 500, 686, 715, 765, 766, 857, 927, 964, 971, 972, 987, 1009, 1010, 1021, 2508, 2509, 2516, 2518, 2536, 2568, 2583, 2585, 3811, 3940, 4130, 4192, 4236, 4512, 4841, 5353, 5532, 5854, 106, 107, 195, 380, 930, 931, 932, 3845, 3846, 3863, 3873, 4141, 4142, 4343, 4344, 4347, 211, 212, 277, 631] + banner_tags = [141, 142, 143, 156, 206, 231, 232, 390, 391, 400, 457, 500, 536, 537, 539, 457, 500, 686, 715, 765, + 766, 857, 927, 964, 971, 972, 987, 1009, 1010, 1021, 2508, 2509, 2516, 2518, 2536, 2568, 2583, 2585, + 3811, 3940, 4130, 4192, 4236, 4512, 4841, 5353, 5532, 5854, 106, 107, 195, 380, 930, 931, 932, 3845, + 3846, 3863, 3873, 4141, 4142, 4343, 4344, 4347, 211, 212, 277, 631] cont = {'theme_search_form': ThemeSearch(), 'expo_catalog': Exposition.catalog, 'book_aid': settings.BOOKING_AID, 'blogs': Article.objects.main_page_blogs(), 'news_list': Article.objects.main_page_news(), 'sng_countries': settings.SNG_COUNTRIES, - 'banner_themes' : banners_themes, 'banner_tags' : banner_tags + 'banner_themes': banners_themes, 'banner_tags': banner_tags, 'seo_text': add_seo(request) } user = request.user if not user.is_anonymous() and not user.url: - cont.update({'reg_complete': RegistrationCompleteForm(instance=user)}) + if not user.email: + cont.update({'reg_complete': SocialRegistrationCompleteForm(instance=user)}) + else: + cont.update({'reg_complete': RegistrationCompleteForm(instance=user)}) if not request.GET: cont.update({'search_form': ExpositionSearchForm()}) return cont + def error404(request): context = {} expo_themes = Theme.active.expo_themes_with_count() @@ -41,7 +70,8 @@ def error404(request): response.status_code = 404 return response -class MainPageView(JitterCacheMixin,TemplateView): + +class MainPageView(JitterCacheMixin, TemplateView): cache_range = settings.CACHE_RANGE template_name = 'index.html' @@ -63,5 +93,6 @@ class MainPageView(JitterCacheMixin,TemplateView): class AdvertisingView(TemplateView): template_name = 'simple_pages/advertising.html' + class AboutView(TemplateView): - template_name = 'simple_pages/about.html' \ No newline at end of file + template_name = 'simple_pages/about.html' diff --git a/registration/backends/default/views.py b/registration/backends/default/views.py index b1edd557..667ea2a3 100644 --- a/registration/backends/default/views.py +++ b/registration/backends/default/views.py @@ -8,7 +8,7 @@ from registration import signals from registration.models import RegistrationProfile from registration.views import ActivationView as BaseActivationView from registration.views import RegistrationView as BaseRegistrationView - +from django.views.generic import View import json @@ -150,7 +150,7 @@ from django.http import HttpResponse, HttpResponseRedirect from accounts.models import User from registration.forms import RegistrationFormUniqueEmail -from django.contrib.auth import login, logout +from django.contrib.auth import login, logout, authenticate from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.cache import never_cache @@ -248,15 +248,29 @@ def LoginView(request): else: HttpResponseRedirect('/') -from accounts.forms import RegistrationCompleteForm, RecoveryForm +from accounts.forms import RegistrationCompleteForm, RecoveryForm, SocialRegistrationCompleteForm +from social.apps.django_app.default.models import UserSocialAuth + + def complete_registration(request): if request.is_ajax(): response = {'success': False} - form = RegistrationCompleteForm(request.POST, instance=request.user) + if request.POST.get('email'): + form = SocialRegistrationCompleteForm(request.POST, instance=request.user) + else: + form = RegistrationCompleteForm(request.POST, instance=request.user) if form.is_valid(): user = form.save() - response['success']=True - response['redirect'] = user.get_permanent_url() + if user != request.user: + try: + social = UserSocialAuth.objects.get(user=request.user) + uid, provider = social.uid, social.provider + User.objects.get(id = request.user.id).delete() + user.social_auth.create(uid=uid, provider=provider) + except UserSocialAuth.DoesNotExist: + pass + response['success']= True + response['redirect'] = '/' else: response['errors'] = form.errors return HttpResponse(json.dumps(response), content_type='application/json') @@ -264,6 +278,22 @@ def complete_registration(request): else: return HttpResponse('not ajax') +from django.shortcuts import render + + +def acquire_email(request, template_name="registration/acquire_email.html"): + """ + Request email for the create user flow for logins that don't specify their email address. + """ + backend = request.session['partial_pipeline']['backend'] + return render(request, template_name, {"backend": backend}) + + +def inactive_user_message(request): + return render(request, 'registration/social_registration_complete.html') + + + def pswd_recovery(request): #if request.is_ajax(): response = {'success': False} diff --git a/settings/management/commands/do_inflect.py b/settings/management/commands/do_inflect.py index 601a69a3..c226f08a 100644 --- a/settings/management/commands/do_inflect.py +++ b/settings/management/commands/do_inflect.py @@ -8,7 +8,7 @@ from city.models import City CITIES = settings.MEDIA_ROOT+'/import/cities_inflect.xls' TAGS = settings.MEDIA_ROOT+'/import/tags_inflect.xls' - +import inspect, os class Command(BaseCommand): def handle(self, *args, **options): diff --git a/settings/redirect_views.py b/settings/redirect_views.py index 74eccd85..67df53d2 100644 --- a/settings/redirect_views.py +++ b/settings/redirect_views.py @@ -131,11 +131,9 @@ class Country_or_City(object): result = 'city/%s'%obj.url return {key: result} - - old_params = {'city': CityRedirect, 'country': CountryRedirect, 'theme': ThemeRedirect, 'tag': TagRedirect, 'event': EventRedirect, 'company': Company, 'article': ArticleRedirect, 'user': UserRedirect, - 'page': PageRedirect, 'news_p': News_p, 'country_or_city': Country_or_City} + 'page': PageRedirect, 'news_p': News_p} def old_redirect(request, *args, **kwargs): diff --git a/templates/admin/accounts/user_change.html b/templates/admin/accounts/user_change.html index 2b31a009..cff1f51f 100644 --- a/templates/admin/accounts/user_change.html +++ b/templates/admin/accounts/user_change.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} + {% extends 'base.html' %} {% load static %} {% block scripts %} @@ -23,8 +23,20 @@ } }); return false; + }); + $('#gen_pass').click(function(){ + $('#id_new_password').val(generatePassword()); }) - }) + }); + function generatePassword() { + var length = 8, + charset = "abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + retVal = ""; + for (var i = 0, n = charset.length; i < length; ++i) { + retVal += charset.charAt(Math.floor(Math.random() * n)); + } + return retVal; + } ").appendTo("head")})})(window,document,jQuery); \ No newline at end of file diff --git a/templates/client/wizard/first_step.html b/templates/client/wizard/first_step.html new file mode 100644 index 00000000..08bcd4db --- /dev/null +++ b/templates/client/wizard/first_step.html @@ -0,0 +1,218 @@ +{% extends 'client/base_catalog.html' %} + +{% block content_list %} + {{ form.errors }} +
+
+
+ {{ wizard.form.media }} +

Шаг {{ wizard.steps.step1 }}. Основная информация

+
+
{% csrf_token %} + {{ wizard.management_form }} + {# {% if wizard.form.forms %}#} + {# {{ wizard.form.management_form }}#} + {# {% for form in wizard.form.forms %}#} + {# {{ form }}#} + {# {% endfor %}#} + {# {% else %}#} + {% with wizard.form as form %} +
+
+
+ + {{ form.theme }} +
+
+ + {{ form.tag }} +
+
+
+ +

Описание выставки

+ +
+
+ {{ form.name }} +
+
+ +
+
+ {{ form.main_title }} +
+
+ +
+
+ {{ form.description }} +
+
+ +
+ +
+ + +
+
+
+ + {{ form.date_start }} +
+
+ + {{ form.date_end }} +
+
+
+
+ +
+ +
+ +

Локация

+ +
+
+

Место проведения:

+ {{ form.place }} +
+
+ +
+
+

Страна:

+ {{ form.country }} +
+ +
+

Город:

+ {{ form.city }} +
+
+ +
+
+ +
+

Дополнительная информация

+ +
+ + +
+ +
+
+ + + +
+ +
+ {{ form.periodic }} +
+
+ +
+ +
+
+ +
+ + +
+ + + +
+
+
+ +
+
+ {{ form.web_site }} + + {{ form.products }} + +
+ +
+
+ +
+ + +
+
+ {#
#} + {# #} + {#
#} + +
+ + {{ form.time_start }} +
+ +
+ + {{ form.time_end }} +
+ + {# #} +
+ +
+
+ +
+
+ + + +
+
+
+ +
+ +
+ +
+ +
+ + {% endwith %} +
+ +
+
+

Шаг 2. Статистика и условия участия

+
+
+ +
+
+

Шаг 3. Добавление фото

+
+
+ +
+
+ {# {% endif %}#} +{% endblock %} diff --git a/templates/client/wizard/second_step.html b/templates/client/wizard/second_step.html new file mode 100644 index 00000000..8ac6c79d --- /dev/null +++ b/templates/client/wizard/second_step.html @@ -0,0 +1,310 @@ +{% extends 'client/base_catalog.html' %} + +{% block content_list %} + {{ form.errors }} +
+

Добавить событие

+
+
+ {{ wizard.form.media }} +
+
+

Шаг 1. Основная информация

+ + +
+
+ + + +
+
+

Шаг 2. Статистика и условия участия

+ +
+
+ 0,9 баллов к рейтингу
+
+
+ +
{% csrf_token %} + {{ wizard.management_form }} + {% if wizard.form.forms %} + {{ wizard.form.management_form }} + {% for form in wizard.form.forms %} + {{ form }} + {% endfor %} + {% else %} + {% with wizard.form as form %} +
+ +
+ +
+ + {{ form.found_year }} +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ {{ form.statistic_year }} +
+ +
+ +
+ {{ form.visitors }} +
+ +
+ {{ form.partisipants }} +
+ +
+ {{ form.square }} + м² +
+
+ +
+
+ +
+ + +
+ +
+
+
+
+ {{ form.countries }} +
+
+
+
+
+ +
+ + + +
+ +
+ +

Стоимость посещения и участия

+ +
+
+ +
+
+ +
Стоимость билетов
+ +
+ +
    + +
  • + {{ form.one_day }} + +
  • + +
  • + {{ form.all_days }} + +
  • + +
+ +
Предварительная регистрация
+ +
+ +
+ +
+ +
    + +
  • + {{ form.pre_one_day }} + +
  • + +
  • + {{ form.pre_all_days }} + +
  • + +
+ +
Регистрация на стойке
+ +
+ +
+ +
+ +
+
+ +
Стоимость аренды 1м²
+ +
    + +
  • + {{ form.equiped }} + +
  • + +
  • + {{ form.unequiped }} + +
  • + +
  • + {{ form.open_square }} + +
  • + +
+ +
+ +
+ + +
+ {{ form.min_square }} + м² +
+
+ +
+ +
+ + +
+ {{ form.registration_depos }} + +
+
+ +
+ +
+ + +
+
+
+ {{ form.deadline_date }} +
+
+
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ + +
+

более полная информация повышает рейтинг вашего мероприятия и позволяет ранжировать + его выше других

+
+
+ +
+ + {% if wizard.steps.prev %} + + + {% endif %} + + +
+
+ +
+
+
+

Шаг 3. Добавление фото

+ + +
+
+
+{% endwith %} +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/templates/client/wizard/wizard.html b/templates/client/wizard/wizard.html new file mode 100644 index 00000000..8735fb4c --- /dev/null +++ b/templates/client/wizard/wizard.html @@ -0,0 +1,23 @@ +{% extends 'client/base_catalog.html' %} +{% block content_list %} + {{ wizard.form.media }} +

Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}

+
{% csrf_token %} + +{{ wizard.management_form }} +{% if wizard.form.forms %} + {{ wizard.form.management_form }} + {% for form in wizard.form.forms %} + {{ form }} + {% endfor %} +{% else %} + {{ wizard.form }} +{% endif %} +
+{% if wizard.steps.prev %} + + +{% endif %} + +
+{% endblock %} \ No newline at end of file diff --git a/templates/done.html b/templates/done.html new file mode 100644 index 00000000..1f404dc1 --- /dev/null +++ b/templates/done.html @@ -0,0 +1 @@ +{{form_data}} \ No newline at end of file diff --git a/templates/registration/acquire_email.html b/templates/registration/acquire_email.html new file mode 100644 index 00000000..17a0c5f0 --- /dev/null +++ b/templates/registration/acquire_email.html @@ -0,0 +1,27 @@ +{% extends "client/base_page.html" %} + +{% block title %}Email confirmation{% endblock %} + +{% block page_body %} + +

Enter your email

+ +{% if form.errors %} +

Please correct the errors below:

+{% endif %} +
{% csrf_token %} +
+
+ + +{# #} +
+
+ +
+ +{% endblock %} + +{% block content-related %} +

Please enter your valid email, to activate account we will send you an email on this e-mail address

+{% endblock %} \ No newline at end of file diff --git a/templates/registration/social_registration_complete.html b/templates/registration/social_registration_complete.html new file mode 100644 index 00000000..755add9b --- /dev/null +++ b/templates/registration/social_registration_complete.html @@ -0,0 +1,8 @@ +{% extends "client/base_page.html" %} + +{% block title %}Registration complete{% endblock %} + +{% block page_body %} +

Check your email

+

An activation link has been sent to the email address you supplied, along with instructions for activating your account.

+{% endblock %} \ No newline at end of file diff --git a/theme/admin.py b/theme/admin.py index 593e18f8..da323984 100644 --- a/theme/admin.py +++ b/theme/admin.py @@ -147,20 +147,17 @@ def search_tag(request): else: qs = Tag.objects.filter(theme__id__in=theme_ids, translations__name__contains=term) - tags = [{'id': tag.id, 'label': '%s(%s)'%(tag.name, tag.theme.name)} for tag in qs] + return HttpResponse(json.dumps(tags), content_type='application/json') - """ - tags = [] - for id in theme_ids: - if not term: - t = Tag.objects.filter(theme__id=id) - else: - t = Tag.objects.filter(theme__id=id, translations__name__contains=term) - cur_theme_tag = [{'id': tag.id, 'label': '%s(%s)'%(tag.name, tag.theme.name)} for tag in t] - tags = tags +cur_theme_tag - """ +def search2(request): + term = request.GET['term'] + if not term: + qs = Tag.objects.filter().order_by('translations__name')[:50] + else: + qs = Tag.objects.filter(translations__name__contains=term)[:50] + tags = [{'id': tag.id, 'text': '%s(%s)'%(tag.name, tag.theme.name)} for tag in qs] return HttpResponse(json.dumps(tags), content_type='application/json') diff --git a/theme/admin_urls.py b/theme/admin_urls.py index a2b34061..c1803d4f 100644 --- a/theme/admin_urls.py +++ b/theme/admin_urls.py @@ -16,4 +16,5 @@ urlpatterns = patterns('theme.admin', url(r'^theme/all/$', ThemeListView.as_view()), url(r'^tag/all/$', TagListView.as_view()), url(r'^tag/search/$', 'search_tag'), + url(r'^tag/search-without-theme/$', 'search2'), ) diff --git a/wizard/__init__.py b/wizard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wizard/forms.py b/wizard/forms.py new file mode 100644 index 00000000..37bcdb7c --- /dev/null +++ b/wizard/forms.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from django import forms +from theme.models import Theme, Tag +from place_exposition.models import PlaceExposition +from city.models import City +from country.models import Country + +choices = ((0, ''), (1.0, u'Ежегодно'), (2.0, u'2 раза в год'), (3.0, u'3 раза в год'), (4.0, u'4 раза в год'), + (5.0, u'5 раз в год'), (0.5, u'Раз в 2 года'), ( + 0.33, u'Раз в 3 года'), (0.25, u'Раз в 4 года')) + + +class ExpoForm1(forms.Form): + """ + main information about exposition + """ + theme = forms.ModelChoiceField(queryset=Theme.objects.filter(id=50)) + tag = forms.ModelChoiceField(queryset=Tag.objects.filter(id=50)) + name = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'placeholder': "Введите название выставки"})) + main_title = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'placeholder': "Краткое описание выставки (необязательно)"})) + description = forms.CharField(widget=forms.Textarea(attrs={'placeholder': "Полное описание выставки", 'cols':30, 'rows':10})) + date_start = forms.DateField(widget=forms.TextInput(attrs={'class':'data dateFrom', 'placeholder':'дд.мм.гг'})) + date_end = forms.DateField(widget=forms.TextInput(attrs={'class':'data dateTo','placeholder':'дд.мм.гг'})) + country = forms.ModelChoiceField(queryset=Country.objects.filter(id=50)) + city = forms.ModelChoiceField(queryset=City.objects.language('ru').filter(id=900052419)) + place = forms.ModelChoiceField(queryset=PlaceExposition.objects.filter(id=50)) + audience1 = forms.BooleanField(required=False) + audience2 = forms.BooleanField(required=False) + audience3 = forms.BooleanField(required=False) + periodic = forms.ChoiceField(choices=choices) + membership1= forms.BooleanField(required=False) + membership2= forms.BooleanField(required=False) + membership3= forms.BooleanField(required=False) + web_site = forms.URLField(required=False, widget=forms.TextInput(attrs={'placeholder': "Веб-сайт (необязательно)"})) + products = forms.CharField(widget=forms.Textarea(attrs={'placeholder': "Экспонируемые продукты", 'cols':30, 'rows':10})) + time_start = forms.TimeField() + time_end = forms.TimeField() + logo = forms.ImageField(widget=forms.FileInput(attrs={'class':"button big icon-clip"}), required=False) + + +class ExpoForm2(forms.Form): + """ + statistics + """ + found_year = forms.IntegerField() + statistic_year = forms.IntegerField(widget=forms.TextInput(attrs={'placeholder':'введите год'})) + visitors = forms.IntegerField(widget=forms.TextInput(attrs={'placeholder':'количество посетителей'})) + partisipants = forms.IntegerField(widget=forms.TextInput(attrs={'placeholder':'количество учасников'})) + square = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'площадь'})) + countries = forms.ModelChoiceField(queryset=Country.objects.filter(id=50)) + + # ticket price + pre_one_day = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'на один день'})) + pre_all_days = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'на все дни'})) + one_day = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'на один день'})) + all_days = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'на все дни'})) + + # rent price + equiped = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'оборудованная площадь'})) + unequiped = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'необорудованная площадь'})) + open_square = forms.FloatField(widget=forms.TextInput(attrs={'placeholder':'открытая площадь'})) + min_square = forms.FloatField() + registration_depos = forms.FloatField() + deadline_date = forms.DateField(widget=forms.TextInput(attrs={'placeholder':'дд.мм.гг'})) + diff --git a/wizard/models.py b/wizard/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/wizard/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/wizard/tests.py b/wizard/tests.py new file mode 100644 index 00000000..501deb77 --- /dev/null +++ b/wizard/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/wizard/urls.py b/wizard/urls.py new file mode 100644 index 00000000..bc42d249 --- /dev/null +++ b/wizard/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import patterns + +from wizard.forms import ExpoForm1, ExpoForm2 +from wizard.views import ExpoWizard + +urlpatterns = patterns('', + (r'^$', ExpoWizard.as_view([ExpoForm1, ExpoForm2])) +) \ No newline at end of file diff --git a/wizard/views.py b/wizard/views.py new file mode 100644 index 00000000..30a3ad5a --- /dev/null +++ b/wizard/views.py @@ -0,0 +1,89 @@ +from django.shortcuts import render_to_response +from django.contrib.formtools.wizard.views import SessionWizardView +from django.core.files.storage import default_storage, FileSystemStorage +import os +from proj import settings +from wizard import forms +from exposition.models import Exposition, Statistic +from functions.form_check import translit_with_separator + + +# defining different template for every form +TEMPLATES = {'0':'client/wizard/first_step.html', '1':'client/wizard/second_step.html'} + + + +class ExpoWizard(SessionWizardView): + location=os.path.join(settings.MEDIA_ROOT, 'temp', 'files') + file_storage = FileSystemStorage(location, settings.MEDIA_URL) + + def done(self, form_list, **kwargs): + upload_file = form_list[0].cleaned_data['logo'] + data = self.get_all_cleaned_data() + expo = Exposition.objects.language('ru').create( + name = data.get('name'), + data_begin = data.get('date_start'), + data_end = data.get('date_end'), + main_title = data.get('main_title'), + description = data.get('description'), + products = data.get('products'), + country = data.get('country', 1), + city = data.get('city', 1), + place = data.get('place', 1), + periodic = data.get('periodic'), + web_page = data.get('web_site'), + logo = data.get('logo'), + + foundation_year = data.get('found_year'), + area = data.get('square'), + price_day = '%i %s'%(data.get('one_day'), self.request.POST['oneDayCurrency1']), + price_all = '%i %s'%(data.get('all_days'), self.request.POST['allDaysCurrency1']), + price_day_bar = '%i %s'%(data.get('pre_one_day'),self.request.POST['oneDayCurrency1']), + price_all_bar = '%i %s'%(data.get('pre_all_days'),self.request.POST['allDaysCurrency1']), + + min_area = data.get('min_square'), + registration_payment = data.get('registration_depos'), + application_deadline = data.get('deadline_date'), + min_closed_area = data.get('unequiped'), + min_open_area = data.get('open_square'), + min_closed_equipped_area = data.get('equiped'), + url = translit_with_separator(data.get('name')), + quality_label= 0, + audience = 0 + ) + expo.tag = [data.get('tag')] + expo.theme = [data.get('theme')] + if data['membership1']: + expo.quality_label = (expo.quality_label| Exposition.quality_label.exporating) + if data['membership2']: + expo.quality_label = (expo.quality_label| Exposition.quality_label.rsva) + if data['membership3']: + expo.quality_label = (expo.quality_label| Exposition.quality_label.ufi) + + if data['audience1']: + expo.audience = (expo.audience | Exposition.audience.experts) + if data['audience2']: + expo.audience = expo.audience | getattr(Exposition.audience, 'experts and consumers') + if data['audience3']: + expo.audience = expo.audience | (getattr(Exposition.audience, 'general public')) + + expo.save() + + Statistic.objects.language().create( + exposition = expo, + year = data.get('statistic_year'), + visitors = data.get('visitors'), + members = data.get('partisipants'), + countries = data.get('countries'), + area = data.get('square') + ) + + if upload_file: + self.file_storage.delete(upload_file.name) + return render_to_response('done.html', { + 'form_data': [form.cleaned_data for form in form_list], + }) + + def get_template_names(self): + return [TEMPLATES[self.steps.current]] +