├── ega ├── __init__.py ├── tests │ ├── __init__.py │ ├── test_models.py │ └── test_views.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── tweet_predictions.py │ │ └── update_matches.py ├── migrations │ ├── __init__.py │ ├── 0005_tournament_image.py │ ├── 0002_auto_20180218_1324.py │ ├── 0003_auto_20180308_1541.py │ ├── 0006_auto_20180414_1818.py │ ├── 0004_auto_20180411_2221.py │ └── 0001_squashed_0009_match_finished.py ├── templatetags │ ├── __init__.py │ └── ega_tags.py ├── templates │ ├── socialaccount │ │ ├── base.html │ │ ├── authentication_error.html │ │ ├── login_cancelled.html │ │ ├── signup.html │ │ └── connections.html │ ├── ega │ │ ├── _form_errors_all_snippet.html │ │ ├── _field_snippet.html │ │ ├── _form_snippet.html │ │ ├── _trends.html │ │ ├── _user_stats.html │ │ ├── _league_detail.html │ │ ├── leagues.html │ │ ├── history.html │ │ ├── _ranking_table.html │ │ ├── profile.html │ │ ├── league_home.html │ │ ├── invite.html │ │ ├── _team_details.html │ │ ├── _next_matches.html │ │ ├── _predictions.html │ │ ├── ranking.html │ │ ├── home.html │ │ ├── stats.html │ │ ├── meta_home.html │ │ ├── next_matches.html │ │ └── match_details.html │ └── account │ │ ├── account_inactive.html │ │ ├── signup_closed.html │ │ ├── password_reset_from_key_done.html │ │ ├── verification_sent.html │ │ ├── email_confirmed.html │ │ ├── password_reset_done.html │ │ ├── password_change.html │ │ ├── password_set.html │ │ ├── verified_email_required.html │ │ ├── email_confirm.html │ │ ├── password_reset_from_key.html │ │ ├── password_reset.html │ │ ├── signup.html │ │ ├── email.html │ │ └── login.html ├── managers.py ├── constants.py ├── urls.py ├── admin.py ├── forms.py └── views.py ├── fenics ├── __init__.py ├── wsgi.py ├── urls.py ├── context_processors.py └── settings.py ├── news ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── import_news.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ └── news_tags.py ├── admin.py ├── templates │ └── news │ │ └── latest_news.html └── models.py ├── other_migrations ├── __init__.py ├── account │ ├── __init__.py │ ├── 0002_auto_20150419_1646.py │ └── 0001_initial.py └── socialaccount │ ├── __init__.py │ ├── 0006_auto_20161226_1656.py │ ├── 0002_auto_20150418_1939.py │ ├── 0004_auto_20150609_1938.py │ ├── 0003_auto_20150419_1726.py │ ├── 0005_auto_20151230_1602.py │ └── 0001_initial.py ├── media └── teams │ ├── Boca.png │ ├── Colon.png │ ├── Lanus.png │ ├── River.png │ ├── Tigre.png │ ├── Union.png │ ├── Velez.png │ ├── Arsenal.png │ ├── Chicago.png │ ├── Crucero.png │ ├── Defensa.png │ ├── Huracan.png │ ├── Newells.png │ ├── Olimpo.png │ ├── Quilmes.png │ ├── Racing.png │ ├── Rafaela.png │ ├── Aldosivi.png │ ├── Argentinos.png │ ├── Banfield.png │ ├── Belgrano.png │ ├── Gimnasia.png │ ├── GodoyCruz.png │ ├── Patronato.png │ ├── SanLorenzo.png │ ├── Sarmiento.png │ ├── Temperley.png │ ├── Estudiantes.png │ ├── SanMartinSJ.png │ ├── AtleticoTucuman.png │ ├── Independiente.png │ └── RosarioCentral.png ├── static ├── images │ ├── logo.gif │ ├── logo.png │ ├── star.jpg │ ├── timer.gif │ ├── facebook.png │ ├── favicon.ico │ ├── github.png │ ├── google.png │ ├── in_icon.png │ ├── loading.gif │ ├── out_icon.png │ ├── twitter.png │ ├── unknown.png │ ├── ball_icon.png │ ├── empty_star.png │ ├── star_alpha.png │ ├── footer-grass.png │ ├── red_card_icon.gif │ ├── selected_star.png │ ├── whistle_icon.png │ ├── testimonials │ │ ├── paul.jpg │ │ ├── diegom.jpg │ │ └── gonzaloh.png │ └── yellow_card_icon.gif ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff └── css │ └── style.css ├── templates ├── flatpages │ └── default.html ├── 404.html ├── 500.html └── base.html ├── requirements.txt ├── manage.py ├── .gitignore ├── .travis.yml ├── README.md └── fixtures └── sample_data.json /ega/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fenics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /news/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ega/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ega/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ega/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ega/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /news/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /news/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /other_migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /news/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ega/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /news/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /other_migrations/account/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /other_migrations/socialaccount/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ega/templates/socialaccount/base.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | -------------------------------------------------------------------------------- /media/teams/Boca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Boca.png -------------------------------------------------------------------------------- /media/teams/Colon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Colon.png -------------------------------------------------------------------------------- /media/teams/Lanus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Lanus.png -------------------------------------------------------------------------------- /media/teams/River.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/River.png -------------------------------------------------------------------------------- /media/teams/Tigre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Tigre.png -------------------------------------------------------------------------------- /media/teams/Union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Union.png -------------------------------------------------------------------------------- /media/teams/Velez.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Velez.png -------------------------------------------------------------------------------- /media/teams/Arsenal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Arsenal.png -------------------------------------------------------------------------------- /media/teams/Chicago.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Chicago.png -------------------------------------------------------------------------------- /media/teams/Crucero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Crucero.png -------------------------------------------------------------------------------- /media/teams/Defensa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Defensa.png -------------------------------------------------------------------------------- /media/teams/Huracan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Huracan.png -------------------------------------------------------------------------------- /media/teams/Newells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Newells.png -------------------------------------------------------------------------------- /media/teams/Olimpo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Olimpo.png -------------------------------------------------------------------------------- /media/teams/Quilmes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Quilmes.png -------------------------------------------------------------------------------- /media/teams/Racing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Racing.png -------------------------------------------------------------------------------- /media/teams/Rafaela.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Rafaela.png -------------------------------------------------------------------------------- /static/images/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/logo.gif -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/logo.png -------------------------------------------------------------------------------- /static/images/star.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/star.jpg -------------------------------------------------------------------------------- /static/images/timer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/timer.gif -------------------------------------------------------------------------------- /media/teams/Aldosivi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Aldosivi.png -------------------------------------------------------------------------------- /media/teams/Argentinos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Argentinos.png -------------------------------------------------------------------------------- /media/teams/Banfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Banfield.png -------------------------------------------------------------------------------- /media/teams/Belgrano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Belgrano.png -------------------------------------------------------------------------------- /media/teams/Gimnasia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Gimnasia.png -------------------------------------------------------------------------------- /media/teams/GodoyCruz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/GodoyCruz.png -------------------------------------------------------------------------------- /media/teams/Patronato.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Patronato.png -------------------------------------------------------------------------------- /media/teams/SanLorenzo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/SanLorenzo.png -------------------------------------------------------------------------------- /media/teams/Sarmiento.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Sarmiento.png -------------------------------------------------------------------------------- /media/teams/Temperley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Temperley.png -------------------------------------------------------------------------------- /static/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/facebook.png -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/github.png -------------------------------------------------------------------------------- /static/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/google.png -------------------------------------------------------------------------------- /static/images/in_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/in_icon.png -------------------------------------------------------------------------------- /static/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/loading.gif -------------------------------------------------------------------------------- /static/images/out_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/out_icon.png -------------------------------------------------------------------------------- /static/images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/twitter.png -------------------------------------------------------------------------------- /static/images/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/unknown.png -------------------------------------------------------------------------------- /media/teams/Estudiantes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Estudiantes.png -------------------------------------------------------------------------------- /media/teams/SanMartinSJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/SanMartinSJ.png -------------------------------------------------------------------------------- /static/images/ball_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/ball_icon.png -------------------------------------------------------------------------------- /static/images/empty_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/empty_star.png -------------------------------------------------------------------------------- /static/images/star_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/star_alpha.png -------------------------------------------------------------------------------- /media/teams/AtleticoTucuman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/AtleticoTucuman.png -------------------------------------------------------------------------------- /media/teams/Independiente.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/Independiente.png -------------------------------------------------------------------------------- /media/teams/RosarioCentral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/media/teams/RosarioCentral.png -------------------------------------------------------------------------------- /static/images/footer-grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/footer-grass.png -------------------------------------------------------------------------------- /static/images/red_card_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/red_card_icon.gif -------------------------------------------------------------------------------- /static/images/selected_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/selected_star.png -------------------------------------------------------------------------------- /static/images/whistle_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/whistle_icon.png -------------------------------------------------------------------------------- /static/images/testimonials/paul.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/testimonials/paul.jpg -------------------------------------------------------------------------------- /static/images/yellow_card_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/yellow_card_icon.gif -------------------------------------------------------------------------------- /static/images/testimonials/diegom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/testimonials/diegom.jpg -------------------------------------------------------------------------------- /static/images/testimonials/gonzaloh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/images/testimonials/gonzaloh.png -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramiro/fenics/master/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /ega/templates/ega/_form_errors_all_snippet.html: -------------------------------------------------------------------------------- 1 | {% for error in form.non_field_errors %} 2 |

{{ error }}

3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content-title %}{{ flatpage.title }}{% endblock %} 4 | 5 | {% block content %} 6 | {{ flatpage.content|safe }} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.0.2 2 | Pillow==5.0.0 3 | TwitterAPI==2.4.8 4 | demiurge==0.2 5 | django-allauth==0.35.0 6 | django-nocaptcha-recaptcha==0.0.20 7 | feedparser==5.2.1 8 | psycopg2==2.7.4 9 | pygal==2.4.0 10 | -------------------------------------------------------------------------------- /ega/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Account Inactive" %}{% endblock %} 6 | 7 | {% block content %} 8 |

{% trans "This account is inactive." %}

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /news/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from news.models import News 4 | 5 | 6 | class NewsAdmin(admin.ModelAdmin): 7 | list_display = ('title', 'source', 'published') 8 | search_fields = ('title', 'source') 9 | 10 | admin.site.register(News, NewsAdmin) 11 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fenics.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /ega/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Sign Up Closed" %}{% endblock %} 6 | 7 | {% block content %} 8 |

{% trans "We are sorry, but the sign up is currently closed." %}

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /news/templates/news/latest_news.html: -------------------------------------------------------------------------------- 1 | {% for item in news_items %} 2 |

3 | {{ item.title|striptags|safe }}
4 | {{ item.summary|striptags|truncatewords:50|safe }} 5 |
Leer más

6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /ega/templates/socialaccount/authentication_error.html: -------------------------------------------------------------------------------- 1 | {% extends "socialaccount/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Social Network Login Failure" %}{% endblock %} 6 | 7 | {% block content %} 8 |

{% trans "An error occurred while attempting to login via your social network account." %}

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /news/templatetags/news_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from news.models import News 4 | 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.inclusion_tag('news/latest_news.html') 10 | def latest_news(source, latest=3): 11 | news = News.objects.filter(source=source).order_by('-published')[:latest] 12 | return {'news_items': news} 13 | -------------------------------------------------------------------------------- /ega/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |

{% trans "Change Password" %}

11 |

{% trans 'Your password is now changed.' %}

12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | # Translations 24 | *.mo 25 | 26 | # Env 27 | .env 28 | 29 | #Mr Developer 30 | .mr.developer.cfg 31 | 32 | fenics/local_settings.py 33 | env/ 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | dist: trusty 4 | 5 | python: 6 | - "3.5" 7 | - "3.6" 8 | 9 | services: postgresql 10 | 11 | env: 12 | - DJANGO_VERSION=2.0.2 13 | 14 | install: 15 | - pip install -r requirements.txt 16 | 17 | before_script: 18 | - psql -U postgres -c 'create database travisdb;' 19 | 20 | script: 21 | - SECRET_KEY=testkey DATABASE_URL='postgres://localhost/travisdb' python manage.py test 22 | -------------------------------------------------------------------------------- /ega/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block content %} 8 |

{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /fenics/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for fenics project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fenics.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block content-title %}{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |

10 | 11 |
12 |

Tiraste la pelota afuera... acá no está lo que estás buscando.
13 | Volvé a sacar del medio

14 |
15 |
16 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /fenics/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import include, path 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | 6 | admin.autodiscover() 7 | 8 | urlpatterns = [ 9 | path('accounts/', include('allauth.urls')), 10 | path('admin/', admin.site.urls), 11 | path('', include('ega.urls')), 12 | ] 13 | 14 | if settings.DEBUG: 15 | urlpatterns += static( 16 | settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 17 | -------------------------------------------------------------------------------- /templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block content-title %}{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |

10 | 11 |
12 |

Ooops... Quedamos en offside. Culpa nuestra, estamos revisando el problema.
13 | Volvé a sacar del medio

14 |
15 |
16 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /ega/templates/socialaccount/login_cancelled.html: -------------------------------------------------------------------------------- 1 | {% extends "socialaccount/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Login Cancelled" %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 | {% url 'account_login' as login_url %} 10 | 11 |

{% blocktrans %}You decided to cancel logging in to our site using one of your existing accounts. If this was a mistake, please proceed to sign in.{% endblocktrans %}

12 | 13 | {% endblock %} 14 | 15 | -------------------------------------------------------------------------------- /news/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class News(models.Model): 5 | title = models.CharField(max_length=256) 6 | source = models.CharField(max_length=64, blank=True) 7 | published = models.DateTimeField() 8 | summary = models.TextField() 9 | link = models.URLField() 10 | 11 | class Meta: 12 | verbose_name = 'News' 13 | verbose_name_plural = 'News' 14 | ordering = ['-published'] 15 | 16 | def __unicode__(self): 17 | return self.title 18 | -------------------------------------------------------------------------------- /ega/templates/account/email_confirmed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block content-title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | {% block content %} 9 | 10 | {% user_display email_address.user as user_display %} 11 |

{% blocktrans with email_address.email as email %}You have confirmed that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /ega/migrations/0005_tournament_image.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-04-14 16:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ega', '0004_auto_20180411_2221'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='tournament', 15 | name='image', 16 | field=models.ImageField(blank=True, null=True, upload_to='tournaments'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /ega/templates/ega/_field_snippet.html: -------------------------------------------------------------------------------- 1 | {% if field.is_hidden %} 2 | {{ field }} 3 | {% else %} 4 |
5 |
6 | {{ field }} 7 | {% if field.errors %} 8 | 9 | {% for error in field.errors %}{{ error }}{% endfor %} 10 | 11 | {% endif %} 12 |

{{ field.help_text }}

13 |
14 | {% endif %} 15 | -------------------------------------------------------------------------------- /ega/migrations/0002_auto_20180218_1324.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-02-18 16:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ega', '0001_squashed_0009_match_finished'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='egauser', 15 | name='last_name', 16 | field=models.CharField(blank=True, max_length=150, verbose_name='last name'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /other_migrations/account/0002_auto_20150419_1646.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('account', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='emailaddress', 16 | name='email', 17 | field=models.EmailField(unique=True, max_length=254, verbose_name='e-mail address'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /fenics/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from ega.models import Tournament 4 | 5 | 6 | def disqus_shortname(request): 7 | """Adds disqus shortname setting as variable to the context.""" 8 | return {'DISQUS_SHORTNAME': settings.DISQUS_SHORTNAME} 9 | 10 | 11 | def available_tournaments(request): 12 | """Adds available tournaments information as variable to the context.""" 13 | available = Tournament.objects.filter( 14 | published=True, finished=False).order_by('name') 15 | return {'available_tournaments': available} 16 | -------------------------------------------------------------------------------- /ega/templates/ega/_form_snippet.html: -------------------------------------------------------------------------------- 1 |
2 | {% csrf_token %} 3 | 4 | {% for field in form.visible_fields %} 5 | {% include 'ega/_field_snippet.html' %} 6 | {% endfor %} 7 | 8 | {% for field in form.hidden_fields %} 9 | {{ field }} 10 | {% endfor %} 11 | 12 | {{ form_next_field }} 13 | 14 |
15 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /ega/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block content-title %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 | 12 |

{% trans "Password Reset" %}

13 | {% if user.is_authenticated %} 14 | {% include "account/snippets/already_logged_in.html" %} 15 | {% endif %} 16 | 17 |

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

18 | 19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /other_migrations/socialaccount/0006_auto_20161226_1656.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2016-12-26 19:56 3 | from __future__ import unicode_literals 4 | 5 | import allauth.socialaccount.fields 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('socialaccount', '0005_auto_20151230_1602'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='socialaccount', 18 | name='extra_data', 19 | field=allauth.socialaccount.fields.JSONField(default=dict, verbose_name='extra data'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /ega/templates/ega/_trends.html: -------------------------------------------------------------------------------- 1 | {% if values %} 2 |
3 |
4 | {% firstof home_team.code 'Local' %} {{ values.L }}% 5 |
6 |
7 | Empate {{ values.E }}% 8 |
9 |
10 | {% firstof away_team.code 'Visitante' %} {{ values.V }}% 11 |
12 |
13 | {% else %} 14 |
15 | (sin pronósticos) 16 |
17 | {% endif %} 18 | -------------------------------------------------------------------------------- /ega/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
10 |
11 | 12 |

{% trans "Change Password" %}

13 |
14 | {% csrf_token %} 15 | 16 | {% include 'ega/_form_errors_all_snippet.html' %} 17 | 18 | {% for field in form %} 19 | {% include 'ega/_field_snippet.html' %} 20 | {% endfor %} 21 | 22 | 23 |
24 | 25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /ega/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
10 |
11 | 12 |

{% trans "Set Password" %}

13 |
14 | {% csrf_token %} 15 | 16 | {% include 'ega/_form_errors_all_snippet.html' %} 17 | 18 | {% for field in form %} 19 | {% include 'ega/_field_snippet.html' %} 20 | {% endfor %} 21 | 22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /ega/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 | {% url 'account_email' as email_url %} 10 | 11 |

{% blocktrans %}This part of the site requires us to verify that 12 | you are who you claim to be. For this purpose, we require that you 13 | verify ownership of your e-mail address. {% endblocktrans %}

14 | 15 |

{% blocktrans %}We have sent an e-mail to you for 16 | verification. Please click on the link inside this e-mail. Please 17 | contact us if you do not receive it within a few minutes.{% endblocktrans %}

18 | 19 |

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /ega/migrations/0003_auto_20180308_1541.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-03-08 18:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ega', '0002_auto_20180218_1324'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='match', 15 | name='pk_away_goals', 16 | field=models.IntegerField(blank=True, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='match', 20 | name='pk_home_goals', 21 | field=models.IntegerField(blank=True, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name='prediction', 25 | name='penalties', 26 | field=models.CharField(blank=True, max_length=1), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /ega/managers.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from ega.constants import DEFAULT_TOURNAMENT 4 | 5 | 6 | class PredictionManager(models.Manager): 7 | """Prediction manager. 8 | 9 | Forces preload of related team and team stats instances. 10 | """ 11 | 12 | def get_queryset(self): 13 | return super( 14 | PredictionManager, self).get_queryset().select_related( 15 | 'match__home', 'match__away') 16 | 17 | 18 | class LeagueManager(models.Manager): 19 | """League manager.""" 20 | 21 | def current(self): 22 | qs = self.get_queryset().filter(tournament__slug=DEFAULT_TOURNAMENT) 23 | return qs 24 | 25 | 26 | class TeamStatsManager(models.Manager): 27 | """TeamStat manager.""" 28 | 29 | def get_queryset(self): 30 | return super( 31 | TeamStatsManager, self).get_queryset().select_related('team') 32 | -------------------------------------------------------------------------------- /other_migrations/socialaccount/0002_auto_20150418_1939.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('socialaccount', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='socialaccount', 16 | name='provider', 17 | field=models.CharField(max_length=30, verbose_name='provider', choices=[('twitter', 'Twitter'), ('google', 'Google'), ('facebook', 'Facebook')]), 18 | ), 19 | migrations.AlterField( 20 | model_name='socialapp', 21 | name='provider', 22 | field=models.CharField(max_length=30, verbose_name='provider', choices=[('twitter', 'Twitter'), ('google', 'Google'), ('facebook', 'Facebook')]), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /ega/templates/ega/_user_stats.html: -------------------------------------------------------------------------------- 1 | {% load humanize %} 2 | {% load staticfiles %} 3 | 4 |
5 | 6 | {% if request.user.avatar %} 7 | 8 | {% else %} 9 | 10 | {% endif %} 11 | 12 |
13 |

{{ user.username }}

14 | {% if user_position %}Posición: {{ user_position|ordinal }}
{% endif %} 15 | {{ stats.score }} punto{{ stats.score|pluralize }}
16 | {{ stats.count }} partido{{ stats.count|pluralize }} jugado{{ stats.count|pluralize }}
17 | {{ stats.winners }} acierto{{ stats.winners|pluralize }}
18 | {{ stats.exacts }} exacto{{ stats.exacts|pluralize }} 19 |
20 |
21 | -------------------------------------------------------------------------------- /ega/templates/socialaccount/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "socialaccount/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Sign Up" %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 |

Estás por loguearte en {{ site.name }} usando tu cuenta de 10 | {{ account.get_provider.name }}, lo único que falta es que por favor 11 | completes los siguientes datos:

12 | 13 |
14 | {% csrf_token %} 15 | 16 | {% include 'ega/_form_errors_all_snippet.html' %} 17 | 18 | {% for field in form %} 19 | {% include 'ega/_field_snippet.html' %} 20 | {% endfor %} 21 | 22 | {% if redirect_field_value %} 23 | 24 | {% endif %} 25 | 26 |
27 | 28 |
29 |
30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /news/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='News', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('title', models.CharField(max_length=256)), 18 | ('source', models.CharField(max_length=64, blank=True)), 19 | ('published', models.DateTimeField()), 20 | ('summary', models.TextField()), 21 | ('link', models.URLField()), 22 | ], 23 | options={ 24 | 'ordering': ['-published'], 25 | 'verbose_name': 'News', 26 | 'verbose_name_plural': 'News', 27 | }, 28 | bases=(models.Model,), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /ega/templates/ega/_league_detail.html: -------------------------------------------------------------------------------- 1 | {% load humanize %} 2 | 3 |
4 |
5 |

{{ league.name }} 6 | {% if league.owner == request.user %} 7 | 10 | {% endif %} 11 |

12 |
13 | 17 | 26 |
27 | -------------------------------------------------------------------------------- /ega/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block content-title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | {% block content %} 9 | 10 | {% if confirmation %} 11 | 12 | {% user_display confirmation.email_address.user as user_display %} 13 |

Por favor confirmá que {{ confirmation.email_address.email }} 14 | es una dirección válida para el usuario {{ user_display }}.

15 | 16 |
17 | {% csrf_token %} 18 |
19 | 20 |
21 |
22 | 23 | {% else %} 24 | 25 | {% url 'account_email' as email_url %} 26 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. 27 | Please issue a new e-mail confirmation request. 28 | {% endblocktrans %}

29 | 30 | {% endif %} 31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /other_migrations/socialaccount/0004_auto_20150609_1938.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('socialaccount', '0003_auto_20150419_1726'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='socialaccount', 16 | name='provider', 17 | field=models.CharField(max_length=30, choices=[('facebook', 'Facebook'), ('twitter', 'Twitter'), ('google', 'Google')], verbose_name='provider'), 18 | ), 19 | migrations.AlterField( 20 | model_name='socialapp', 21 | name='provider', 22 | field=models.CharField(max_length=30, choices=[('facebook', 'Facebook'), ('twitter', 'Twitter'), ('google', 'Google')], verbose_name='provider'), 23 | ), 24 | migrations.AlterField( 25 | model_name='socialtoken', 26 | name='token', 27 | field=models.TextField(help_text='"oauth_token" (OAuth1) or access token (OAuth2)', verbose_name='token'), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /other_migrations/socialaccount/0003_auto_20150419_1726.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import allauth.socialaccount.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('socialaccount', '0002_auto_20150418_1939'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='socialaccount', 17 | name='extra_data', 18 | field=allauth.socialaccount.fields.JSONField(default='{}', verbose_name='extra data'), 19 | ), 20 | migrations.AlterField( 21 | model_name='socialaccount', 22 | name='provider', 23 | field=models.CharField(max_length=30, verbose_name='provider', choices=[('facebook', 'Facebook'), ('google', 'Google'), ('twitter', 'Twitter')]), 24 | ), 25 | migrations.AlterField( 26 | model_name='socialapp', 27 | name='provider', 28 | field=models.CharField(max_length=30, verbose_name='provider', choices=[('facebook', 'Facebook'), ('google', 'Google'), ('twitter', 'Twitter')]), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /ega/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
10 |
11 | 12 |

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

13 | {% if token_fail %} 14 | {% url 'account_reset_password' as passwd_reset_url %} 15 |

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

16 | {% else %} 17 | {% if form %} 18 |
19 | {% csrf_token %} 20 | 21 | {% include 'ega/_form_errors_all_snippet.html' %} 22 | 23 | {% for field in form %} 24 | {% include 'ega/_field_snippet.html' %} 25 | {% endfor %} 26 | 27 | 28 |
29 | {% else %}

{% trans 'Your password is now changed.' %}

{% endif %} 30 | {% endif %} 31 |
32 |
33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /ega/templates/ega/leagues.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | 4 | {% block content-title %}Ligas de amigos{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |

Nueva liga

10 |
11 | {% csrf_token %} 12 | {% for field in form %} 13 |
14 | 15 | {{ field }} 16 | {% if field.errors %} 17 | 18 | {% for error in field.errors %}{{ error }}{% endfor %} 19 | 20 | {% endif %} 21 |
22 | {% endfor %} 23 | 24 |
25 |
26 | 27 |
28 |

Tus ligas en {{ tournament.name }}

29 | {% for league in leagues %} 30 | {% include 'ega/_league_detail.html' %} 31 | {% empty %} 32 |

No tenés ligas de amigos... create una ahora!

33 | {% endfor %} 34 | 35 |
36 | 37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /ega/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block content-title %}{% endblock %} 7 | 8 | {% block content %} 9 | 10 |
11 |
12 | 13 |

{% trans "Password Reset" %}

14 | {% if user.is_authenticated %} 15 | {% include "account/snippets/already_logged_in.html" %} 16 | {% endif %} 17 | 18 |

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

19 | 20 |
21 | {% csrf_token %} 22 | 23 | {% include 'ega/_form_errors_all_snippet.html' %} 24 | 25 | {% for field in form %} 26 | {% include 'ega/_field_snippet.html' %} 27 | {% endfor %} 28 | 29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 | {% endblock %} 38 | 39 | {% block extra_body %} 40 | 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /ega/migrations/0006_auto_20180414_1818.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-04-14 21:18 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('ega', '0005_tournament_image'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='match', 16 | name='away_placeholder', 17 | field=models.CharField(blank=True, max_length=200), 18 | ), 19 | migrations.AddField( 20 | model_name='match', 21 | name='home_placeholder', 22 | field=models.CharField(blank=True, max_length=200), 23 | ), 24 | migrations.AlterField( 25 | model_name='match', 26 | name='away', 27 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='away_games', to='ega.Team'), 28 | ), 29 | migrations.AlterField( 30 | model_name='match', 31 | name='home', 32 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='home_games', to='ega.Team'), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /ega/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | EMAILS_PLACEHOLDER = 'Los emails de tus amigos, separados por coma' 4 | 5 | INVITE_BODY = """Hola! 6 | 7 | Estoy jugando en el Ega, pronosticando los resultados del 8 | Torneo de Transición 2014, estaría bueno si venís a participar conmigo. 9 | %(extra_text)s 10 | Podés unirte siguiendo el link: %(url)s 11 | 12 | Saludos! 13 | %(inviter)s""" 14 | 15 | INVITE_LEAGUE = """ 16 | Para hacer las cosas más interesantes, creé una liga de amigos, en donde vamos 17 | a tener una tabla de posiciones separada de la general y vamos a poder comentar 18 | y discutir opiniones entre nosotros. Esta liga se llama %(league_name)s. 19 | """ 20 | 21 | INVITE_SUBJECT = 'Sumate a "el Ega"' 22 | 23 | LEAGUE_JOIN_CHOICES = [ 24 | ('tweet', 'Link followed from tweet.'), 25 | ('email', 'Link followed from email.'), 26 | ('self', 'User joined a league by himself.'), 27 | ] 28 | 29 | 30 | # Game settings 31 | 32 | DEFAULT_TOURNAMENT = 'primera-division' 33 | 34 | NEXT_MATCHES_DAYS = 90 35 | HOURS_TO_DEADLINE = 0 36 | 37 | EXACTLY_MATCH_POINTS = 3 38 | WINNER_MATCH_POINTS = 1 39 | STARRED_MATCH_POINTS = 1 40 | 41 | MATCH_WON_POINTS = 3 42 | MATCH_TIE_POINTS = 1 43 | MATCH_LOST_POINTS = 0 44 | 45 | HISTORY_MATCHES_PER_PAGE = 15 46 | RANKING_TEAMS_PER_PAGE = 10 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stories in Ready](https://badge.waffle.io/el-ega/fenics.svg?label=ready&title=ToDo)](http://waffle.io/el-ega/fenics) 2 | 3 | fenics 4 | ====== 5 | 6 | fenics es el proyecto detrás de https://el-ega.com.ar, 7 | el sitio de pronósticos deportivos. 8 | 9 | Seguí el desarrollo en https://waffle.io/el-ega/fenics. 10 | 11 | Cómo levantar un entorno de desarrollo 12 | -------------------------------------- 13 | 14 | ### Algunas dependencias previas 15 | 16 | $ sudo apt-get install python-virtualenv python3-dev libxml2-dev libxslt-dev libjpeg-dev zlib1g-dev 17 | 18 | 19 | ### Levantando el proyecto 20 | 21 | $ git clone git@github.com:el-ega/fenics 22 | $ cd fenics 23 | $ virtualenv -p python3 --system-site-packages env 24 | $ source env/bin/activate 25 | (env) $ pip install -r requirements.txt 26 | (env) $ python manage.py migrate 27 | (env) $ python manage.py loaddata fixtures/sample_data.json 28 | (env) $ python manage.py runserver 29 | 30 | Acceder a través del browser en http://localhost:8000/ 31 | 32 | 33 | ### Cargando información 34 | 35 | Opcionalmente, crear un super usuario para administrar el sitio: 36 | 37 | (env) $ python manage.py createsuperuser 38 | 39 | Para cargar partidos/resultados del torneo actual: 40 | 41 | (env) $ python manage.py update_matches 42 | 43 | Para cargar noticias: 44 | 45 | (env) $ python manage.py import_news 46 | -------------------------------------------------------------------------------- /ega/templates/ega/history.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block content-title %}Historial{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 | {% include 'ega/_predictions.html' with history=predictions %} 11 | 12 |
13 |
    14 | {% if predictions.has_previous %} 15 |
  • «
  • 16 | {% endif %} 17 | {% if predictions.paginator.num_pages > 1 %} 18 | {% for p in predictions.paginator.page_range %} 19 |
  • 20 | {{ p }} 21 |
  • 22 | {% endfor %} 23 | {% endif %} 24 | {% if predictions.has_next %} 25 |
  • »
  • 26 | {% endif %} 27 |
28 |
29 | 30 |
31 | 34 |
35 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /ega/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% endblock %} 6 | 7 | {% block extra-head %} 8 | 9 | {% endblock %} 10 | 11 | {% block content %} 12 | 13 |
14 |
15 | 16 |

{% trans "Sign Up" %}

17 | 18 |

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

19 | 20 |
21 | {% csrf_token %} 22 | 23 | {% include 'ega/_field_snippet.html' with field=form.username %} 24 | {% include 'ega/_field_snippet.html' with field=form.email %} 25 | {% include 'ega/_field_snippet.html' with field=form.password1 %} 26 | {% include 'ega/_field_snippet.html' with field=form.password2 %} 27 | 28 | {% include 'ega/_field_snippet.html' with field=form.captcha %} 29 | 30 | {% if redirect_field_value %} 31 | 32 | {% endif %} 33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /ega/templates/ega/_ranking_table.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% if score_details %} 10 | 11 | 12 | 13 | {% endif %} 14 | 15 | 16 | 17 | 18 | {% for row in ranking %} 19 | 20 | 21 | 27 | 28 | {% if score_details %} 29 | 30 | 31 | 32 | {% endif %} 33 | 34 | 35 | {% endfor %} 36 | 37 |
#Usuariox1x3Puntos
{{ forloop.counter0|add:delta }}{% if row.avatar %} 22 | 23 | {% else %} 24 | 25 | {% endif %} 26 | {{ row.username }}{{ row.x1|add:row.xx1 }}{{ row.x3|add:row.xx3 }}{% if row.xx1 or row.xx3 %}+{{ row.xx1|add:row.xx3 }}{% endif %}{{ row.total }}
38 | -------------------------------------------------------------------------------- /ega/migrations/0004_auto_20180411_2221.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.2 on 2018-04-12 01:21 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('ega', '0003_auto_20180308_1541'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ChampionPrediction', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('score', models.PositiveIntegerField(default=0)), 20 | ('last_updated', models.DateTimeField(auto_now=True)), 21 | ('log', models.TextField(blank=True)), 22 | ('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ega.Team')), 23 | ('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.Tournament')), 24 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | migrations.AddField( 28 | model_name='prediction', 29 | name='last_updated', 30 | field=models.DateTimeField(auto_now=True, null=True), 31 | ), 32 | migrations.AlterUniqueTogether( 33 | name='championprediction', 34 | unique_together={('user', 'tournament')}, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /ega/templates/ega/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block content-title %}{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 | {% if request.user.avatar %} 11 | 12 | {% else %} 13 | 14 | {% endif %} 15 |
16 |
17 | 18 |
19 | {% csrf_token %} 20 | 21 | {% include 'ega/_form_errors_all_snippet.html' %} 22 | 23 | {% for field in form %} 24 | {% include 'ega/_field_snippet.html' %} 25 | {% endfor %} 26 | 27 | 28 |
    29 | {% for email in request.user.emailaddress_set.all %} 30 |
  • 31 | {{ email.email }} 32 | {% if not email.verified %}verificar 33 | {% else %}verificada{% endif %} 34 | {% if email.primary %}principal{% endif %} 35 |
  • 36 | {% endfor%} 37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 |
45 | 46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /ega/templates/socialaccount/connections.html: -------------------------------------------------------------------------------- 1 | {% extends "socialaccount/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "Account Connections" %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 | {% if form.accounts %} 10 |

{% blocktrans %} 11 | You can sign in to your account using any of the following third party accounts: 12 | {% endblocktrans %}

13 | 14 |
15 | {% csrf_token %} 16 | 17 |
18 | {% if form.non_field_errors %} 19 |
{{form.non_field_errors}}
20 | {% endif %} 21 | 22 | {% for base_account in form.accounts %} 23 | {% with base_account.get_provider_account as account %} 24 |
25 | 30 |
31 | {% endwith %} 32 | {% endfor %} 33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | {% else %} 43 |

{% trans 'You currently have no social network accounts connected to this account.' %}

44 | {% endif %} 45 | 46 |

{% trans 'Add a 3rd Party Account' %}

47 | 48 | 51 | 52 | {% include "socialaccount/snippets/login_extra.html" %} 53 | 54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /ega/templates/ega/league_home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block content-title %}Liga {{ league.name }}{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 | {% include 'ega/_user_stats.html' %} 11 | 12 | {% if top_ranking %} 13 |

14 | Posiciones 15 |

16 | {% include 'ega/_ranking_table.html' with ranking=top_ranking delta=1 %} 17 |

18 | 19 | ver tabla completa

20 | {% endif %} 21 |
22 | 23 |
24 | 25 |
26 | {% include 'ega/_league_detail.html' %} 27 |
28 | 39 |
40 |
41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /ega/templates/ega/invite.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | {% load staticfiles %} 4 | 5 | {% block content-title %} 6 | {% if league %} 7 | Invitar amigos a la liga {{ league}} 8 | {% else %} 9 | Invitar amigos a el Ega{% endif %} 10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 |

Invitá a tus amigos compartiendo un link en 15 | 16 | 17 | 18 | 19 | 20 | 21 | o como quieras!

22 |

Tu link para referir usuarios es este: 23 | {{ invite_url }} 24 |

25 | 26 | {% comment %} 27 | y/o vía email usando el siguiente formulario:

28 | 29 |
30 | {% csrf_token %} 31 | 32 | {% include 'ega/_form_errors_all_snippet.html' %} 33 | 34 | {% for field in form %} 35 | {% include 'ega/_field_snippet.html' %} 36 | {% endfor %} 37 | 38 |
39 | 40 |
41 | Una copia de este email será enviado a los administradores del 42 | sitio. 43 |
44 |
45 | {% endcomment %} 46 | 47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /ega/templates/ega/_team_details.html: -------------------------------------------------------------------------------- 1 | {% load ega_tags %} 2 |
3 |
4 |
5 | {{ team|default_if_none:placeholder }} 6 | {% if team.image %} 7 | 8 | {% endif %}
9 |
10 |
11 | {% if stats %} 12 |

{# 1ero | #} 13 | {{ stats.won }} ganado{{ stats.won|pluralize }}, 14 | {{ stats.tie }} empatado{{ stats.tie|pluralize }}, 15 | {{ stats.lost }} perdido{{ stats.lost|pluralize }}

16 | {% endif %} 17 | {% get_latest_matches team tournament as latest_matches %} 18 | {% if latest_matches %} 19 | 20 | 21 | 22 | 23 | {% for m in latest_matches|slice:':3' %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 |
Últimos resultados
{{ m.home.name }}{{ m.home_goals|default_if_none:'-' }}{{ m.away_goals|default_if_none:'-' }}{{ m.away.name }}
33 |
34 | {% endif %} 35 |
36 |
37 | -------------------------------------------------------------------------------- /other_migrations/socialaccount/0005_auto_20151230_1602.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9 on 2015-12-30 19:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('socialaccount', '0004_auto_20150609_1938'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='socialaccount', 17 | name='provider', 18 | field=models.CharField(max_length=30, verbose_name='provider'), 19 | ), 20 | migrations.AlterField( 21 | model_name='socialaccount', 22 | name='uid', 23 | field=models.CharField(max_length=191, verbose_name='uid'), 24 | ), 25 | migrations.AlterField( 26 | model_name='socialapp', 27 | name='client_id', 28 | field=models.CharField(help_text='App ID, or consumer key', max_length=191, verbose_name='client id'), 29 | ), 30 | migrations.AlterField( 31 | model_name='socialapp', 32 | name='key', 33 | field=models.CharField(blank=True, help_text='Key', max_length=191, verbose_name='key'), 34 | ), 35 | migrations.AlterField( 36 | model_name='socialapp', 37 | name='provider', 38 | field=models.CharField(max_length=30, verbose_name='provider'), 39 | ), 40 | migrations.AlterField( 41 | model_name='socialapp', 42 | name='secret', 43 | field=models.CharField(help_text='API secret, client secret, or consumer secret', max_length=191, verbose_name='secret key'), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /news/management/commands/import_news.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | 4 | import feedparser 5 | 6 | from django.conf import settings 7 | from django.core.management.base import BaseCommand, CommandError 8 | from django.utils.timezone import make_aware 9 | 10 | from news.models import News 11 | 12 | 13 | class Command(BaseCommand): 14 | help = 'Import news from feed' 15 | 16 | def handle(self, *args, **options): 17 | for source, feed_url in settings.NEWS_FEEDS: 18 | feed = feedparser.parse(feed_url) 19 | 20 | for entry in feed['entries']: 21 | news_date = make_aware(datetime.datetime( 22 | year=entry.updated_parsed.tm_year, 23 | month=entry.updated_parsed.tm_mon, 24 | day=entry.updated_parsed.tm_mday, 25 | hour=entry.updated_parsed.tm_hour, 26 | minute=entry.updated_parsed.tm_min)) 27 | 28 | if len(entry['link']) > 200: 29 | continue 30 | 31 | if not News.objects.filter(title=entry['title'], 32 | source=source, 33 | published=news_date): 34 | try: 35 | news = News.objects.create(title=entry['title'], 36 | source=source, 37 | published=news_date, 38 | summary=entry['summary'], 39 | link=entry['link']) 40 | except: 41 | # ignore for now 42 | pass 43 | -------------------------------------------------------------------------------- /ega/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.conf import settings 4 | from django.core import mail 5 | from django.test import TestCase 6 | 7 | from ega.models import EgaUser 8 | from ega.constants import INVITE_SUBJECT, INVITE_BODY 9 | 10 | ADMINS = ['natalia@gmail.com', 'matias@gmail.com'] 11 | 12 | 13 | class EgaUserTestCase(TestCase): 14 | 15 | def setUp(self): 16 | super(EgaUserTestCase, self).setUp() 17 | self.user = EgaUser.objects.create() 18 | 19 | def test_invite_friends_no_emails(self): 20 | self.user.invite_friends([]) 21 | self.assertEqual(len(mail.outbox), 0) 22 | 23 | def test_invite_friends(self): 24 | friends = ['a@a.com', 'b@b.com'] 25 | self.user.invite_friends(friends) 26 | 27 | self.assertEqual(len(mail.outbox), 1) 28 | email = mail.outbox[0] 29 | self.assertEqual(email.subject, INVITE_SUBJECT) 30 | self.assertEqual(email.body, INVITE_BODY) 31 | self.assertEqual(email.from_email, settings.EL_EGA_NO_REPLY) 32 | self.assertEqual(email.to, [settings.EL_EGA_ADMIN]) 33 | self.assertEqual(email.bcc, friends + ADMINS) 34 | 35 | def test_invite_friends_custom_subject_body(self): 36 | friends = ['a@a.com', 'b@b.com'] 37 | subject = 'booga booga' 38 | body = 'lorem ipsum sir amet.' 39 | self.user.invite_friends(friends, subject, body) 40 | 41 | self.assertEqual(len(mail.outbox), 1) 42 | email = mail.outbox[0] 43 | self.assertEqual(email.subject, subject) 44 | self.assertEqual(email.body, body) 45 | self.assertEqual(email.from_email, settings.EL_EGA_NO_REPLY) 46 | self.assertEqual(email.to, [settings.EL_EGA_ADMIN]) 47 | self.assertEqual(email.bcc, friends + ADMINS) 48 | -------------------------------------------------------------------------------- /ega/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | import ega.views 4 | 5 | 6 | urlpatterns = [ 7 | path('', ega.views.meta_home, name='meta-home'), 8 | path('logout/', ega.views.logout, name='logout'), 9 | path('profile/', ega.views.profile, name='profile'), 10 | path('profile/verify//', ega.views.verify_email, 11 | name='verify-email'), 12 | 13 | path('invite/', ega.views.invite_friends, name='ega-invite'), 14 | path('/invite//', 15 | ega.views.invite_friends, name='ega-invite-league'), 16 | path('join///', ega.views.friend_join, 17 | name='ega-join'), 18 | path('join////', 19 | ega.views.friend_join, name='ega-join'), 20 | 21 | path('/', ega.views.home, name='ega-home'), 22 | path('/update-champion', ega.views.update_champion_prediction, 23 | name='ega-update-champion'), 24 | path('/history/', ega.views.history, name='ega-history'), 25 | path('/league/', ega.views.leagues, name='ega-leagues'), 26 | path('/league//', 27 | ega.views.league_home, name='ega-league-home'), 28 | path('/matches/', ega.views.next_matches, 29 | name='ega-next-matches'), 30 | path('/matches//', 31 | ega.views.match_details, name='ega-match-details'), 32 | path('/ranking/', ega.views.ranking, name='ega-ranking'), 33 | path('/ranking/f//', ega.views.ranking, 34 | name='ega-ranking'), 35 | path('/ranking//', 36 | ega.views.ranking, name='ega-league-ranking'), 37 | path('/ranking//f//', 38 | ega.views.ranking, name='ega-league-ranking'), 39 | path('/stats/', ega.views.stats, name='ega-stats'), 40 | ] 41 | -------------------------------------------------------------------------------- /ega/templates/ega/_next_matches.html: -------------------------------------------------------------------------------- 1 | {% for match in matches %} 2 |
3 | {% with home=match.home away=match.away %} 4 |
5 |
6 | {% if home.image %} 7 | 8 | {% endif %} 9 |
10 |
11 | {% firstof home.code home.name match.home_placeholder %} 12 |
13 | {% if match.user_prediction %} 14 |
15 | {{ match.user_prediction.home_goals }}{% if match.user_prediction.penalties_home %}[P]{% endif %} 16 |
17 |
18 | {{ match.user_prediction.away_goals }}{% if match.user_prediction.penalties_away %}[P]{% endif %} 19 |
20 | {% else %} 21 |
22 | {% endif %} 23 |
24 | {% firstof away.code away.name match.away_placeholder %} 25 |
26 |
27 | {% if away.image %} 28 | 29 | {% endif %} 30 |
31 |
32 |
33 |
34 | 35 | {% if match.description %}{{ match.description }} | {% endif %} 36 | {{ match.when|date:"F j, P" }} 37 | 38 |
39 |
40 | {% endwith %} 41 |
42 | {% endfor %} 43 | -------------------------------------------------------------------------------- /other_migrations/account/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.utils.timezone 6 | from django.conf import settings 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='EmailAddress', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 20 | ('email', models.EmailField(unique=True, max_length=75, verbose_name='e-mail address')), 21 | ('verified', models.BooleanField(default=False, verbose_name='verified')), 22 | ('primary', models.BooleanField(default=False, verbose_name='primary')), 23 | ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), 24 | ], 25 | options={ 26 | 'verbose_name': 'email address', 27 | 'verbose_name_plural': 'email addresses', 28 | }, 29 | bases=(models.Model,), 30 | ), 31 | migrations.CreateModel( 32 | name='EmailConfirmation', 33 | fields=[ 34 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 35 | ('created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created')), 36 | ('sent', models.DateTimeField(null=True, verbose_name='sent')), 37 | ('key', models.CharField(unique=True, max_length=64, verbose_name='key')), 38 | ('email_address', models.ForeignKey(verbose_name='e-mail address', to='account.EmailAddress', on_delete=models.CASCADE)), 39 | ], 40 | options={ 41 | 'verbose_name': 'email confirmation', 42 | 'verbose_name_plural': 'email confirmations', 43 | }, 44 | bases=(models.Model,), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /ega/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ega.models import ( 4 | ChampionPrediction, 5 | EgaUser, 6 | League, 7 | LeagueMember, 8 | Match, 9 | Prediction, 10 | Team, 11 | TeamStats, 12 | Tournament, 13 | ) 14 | 15 | 16 | class EgaUserAdmin(admin.ModelAdmin): 17 | list_display = ('username', 'email', 'invite_key', 'date_joined') 18 | search_fields = ('username', 'email', 'invite_key') 19 | readonly_fields = ('list_referrals',) 20 | 21 | def list_referrals(self, obj): 22 | return ', '.join(u.username for u in obj.referrals.all()) 23 | 24 | 25 | class LeagueMemberInline(admin.TabularInline): 26 | model = LeagueMember 27 | extra = 0 28 | 29 | 30 | class LeagueAdmin(admin.ModelAdmin): 31 | list_display = ('name', 'slug', 'tournament') 32 | inlines = [LeagueMemberInline] 33 | 34 | 35 | class TeamAdmin(admin.ModelAdmin): 36 | list_display = ('name', 'code') 37 | prepopulated_fields = dict(slug=('name',)) 38 | 39 | 40 | class TeamStatsAdmin(admin.ModelAdmin): 41 | list_display = ('team', 'tournament', 'zone', 'points') 42 | list_filter = ('tournament', 'team') 43 | 44 | 45 | class MatchAdmin(admin.ModelAdmin): 46 | list_display = ('tournament', 'home', 'home_goals', 'away_goals', 'away') 47 | list_filter = ('tournament', 'when', 'finished') 48 | 49 | 50 | class PredictionAdmin(admin.ModelAdmin): 51 | list_display = ('match', 'user', 'score', 'last_updated') 52 | list_filter = ('match__tournament',) 53 | 54 | 55 | class ChampionPredictionAdmin(admin.ModelAdmin): 56 | list_display = ('user', 'tournament', 'team', 'last_updated') 57 | list_filter = ('tournament',) 58 | 59 | 60 | class TournamentAdmin(admin.ModelAdmin): 61 | filter_horizontal = ('teams',) 62 | list_display = ('name', 'published', 'finished') 63 | prepopulated_fields = dict(slug=('name',)) 64 | 65 | 66 | admin.site.register(ChampionPrediction, ChampionPredictionAdmin) 67 | admin.site.register(EgaUser, EgaUserAdmin) 68 | admin.site.register(League, LeagueAdmin) 69 | admin.site.register(Match, MatchAdmin) 70 | admin.site.register(Prediction, PredictionAdmin) 71 | admin.site.register(Team, TeamAdmin) 72 | admin.site.register(TeamStats, TeamStatsAdmin) 73 | admin.site.register(Tournament, TournamentAdmin) 74 | -------------------------------------------------------------------------------- /ega/management/commands/tweet_predictions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import timedelta 4 | 5 | from django.conf import settings 6 | from django.core.management.base import BaseCommand 7 | from django.db import connection 8 | from django.utils.timezone import now 9 | from TwitterAPI import TwitterAPI 10 | 11 | from ega.models import Match 12 | 13 | 14 | PREDICTION_COUNT_QUERY = """ 15 | SELECT home_goals, away_goals, COUNT(*) AS count FROM ega_prediction 16 | WHERE match_id=%s AND home_goals IS NOT NULL AND away_goals IS NOT NULL 17 | GROUP BY home_goals, away_goals ORDER BY count DESC LIMIT %s 18 | """ 19 | 20 | 21 | def get_top_predictions(match, top=3): 22 | """Return the most common predictions for given match.""" 23 | cursor = connection.cursor() 24 | 25 | cursor.execute(PREDICTION_COUNT_QUERY, [match.id, top]) 26 | rows = cursor.fetchall() 27 | return rows 28 | 29 | 30 | class Command(BaseCommand): 31 | help = 'Tweet admin predictions and predictions count' 32 | 33 | def handle(self, *args, **options): 34 | api = TwitterAPI(**settings.TWITTER_CREDENTIALS) 35 | users = settings.EGA_ADMINS.keys() 36 | 37 | # matches in the last hour 38 | matches = Match.objects.filter( 39 | when__isnull=False, suspended=False, 40 | when__range=(now() - timedelta(minutes=30), now())) 41 | 42 | for m in matches: 43 | predictions = m.prediction_set.filter( 44 | user__username__in=users).order_by('user') 45 | if predictions: 46 | data = ', '.join([ 47 | "%s dice %s %d-%d %s" % ( 48 | settings.EGA_ADMINS[p.user.username], 49 | m.home.code, p.home_goals, 50 | p.away_goals, m.away.code) 51 | for p in predictions 52 | if p.home_goals is not None and p.away_goals is not None]) 53 | tweet = u"En juego: %s vs %s\n%s" % ( 54 | m.home.name, m.away.name, data) 55 | api.request('statuses/update', {'status': tweet}) 56 | 57 | # get predictions count and tweet 58 | counts = get_top_predictions(m) 59 | preds = '\n'.join([ 60 | '#%s %d - %d #%s (%d)' % (m.home.code, r[0], r[1], 61 | m.away.code, r[2]) 62 | for r in counts 63 | ]) 64 | tweet = 'Los resultados más pronosticados:\n' + preds 65 | api.request('statuses/update', {'status': tweet}) 66 | -------------------------------------------------------------------------------- /ega/templatetags/ega_tags.py: -------------------------------------------------------------------------------- 1 | import pygal 2 | 3 | from django import template 4 | from django.db.models import Count, Q 5 | from django.utils.timezone import now 6 | from pygal.style import LightGreenStyle 7 | 8 | from ega.models import ChampionPrediction, Prediction 9 | 10 | 11 | register = template.Library() 12 | 13 | 14 | @register.inclusion_tag('ega/_trends.html') 15 | def show_prediction_trends(match): 16 | """Display a progress bar with prediction trends.""" 17 | values = None 18 | # only consider settled predictions 19 | trends = match.prediction_set.exclude( 20 | trend='').values('trend').annotate(num=Count('trend')) 21 | total = sum(t['num'] for t in trends) 22 | if total > 0: 23 | values = {t: 0 for t in ('L', 'E', 'V')} 24 | for t in trends: 25 | values[t['trend']] = t['num'] * 100 // total 26 | diff = 100 - sum(values.values()) 27 | values[list(values.keys())[-1]] += diff 28 | 29 | return {'home_team': match.home or match.home_placeholder, 30 | 'away_team': match.away or match.away_placeholder, 31 | 'count': total, 'values': values} 32 | 33 | 34 | @register.simple_tag 35 | def get_friends_leagues(user, slug): 36 | return user.league_set.filter(tournament__slug=slug) 37 | 38 | 39 | @register.simple_tag 40 | def get_latest_matches(team, tournament): 41 | if team is None: 42 | return None 43 | return team.latest_matches(tournament) 44 | 45 | 46 | @register.simple_tag 47 | def get_user_stats(user, tournament): 48 | return user.stats(tournament) 49 | 50 | 51 | @register.simple_tag 52 | def get_pending_predictions(user, tournament): 53 | tz_now = now() 54 | total = tournament.match_set.filter(when__gt=tz_now).count() 55 | predicted = Prediction.objects.filter( 56 | home_goals__isnull=False, away_goals__isnull=False, 57 | user=user, match__tournament=tournament, 58 | match__when__gt=tz_now).count() 59 | return total - predicted 60 | 61 | 62 | @register.simple_tag 63 | def champion_predictions_chart(tournament): 64 | data = ChampionPrediction.objects.filter( 65 | tournament=tournament).values('team__name') 66 | data = data.annotate(num=Count('team__name')).order_by('-num') 67 | 68 | font_size = 32 69 | chart_style = LightGreenStyle( 70 | legend_font_size=font_size, 71 | tooltip_font_size=font_size, 72 | label_font_size=font_size-10, 73 | major_label_font_size=font_size 74 | ) 75 | max_num = max(data[0]['num'] + 1, 10) 76 | chart = pygal.HorizontalBar( 77 | style=chart_style, legend_at_bottom=True, 78 | legend_at_bottom_columns=1, range=(1, max_num)) 79 | for e in data[:5]: 80 | chart.add(e['team__name'], e['num']) 81 | 82 | return chart.render_data_uri() 83 | -------------------------------------------------------------------------------- /ega/templates/account/email.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block content-title %}{% trans "E-mail Addresses" %}{% endblock %} 6 | 7 | {% block content %} 8 | {% if user.emailaddress_set.all %} 9 |

{% trans 'The following e-mail addresses are associated with your account:' %}

10 | 11 |
12 | {% csrf_token %} 13 |
14 | 15 | {% for emailaddress in user.emailaddress_set.all %} 16 |
17 | 29 |
30 | {% endfor %} 31 | 32 |
33 | 34 | 35 | 36 |
37 | 38 |
39 |
40 | 41 | {% else %} 42 |

{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

43 | 44 | {% endif %} 45 | 46 |

{% trans "Add E-mail Address" %}

47 | 48 |
49 | {% csrf_token %} 50 | 51 | {% include 'ega/_form_errors_all_snippet.html' %} 52 | 53 | {% for field in form %} 54 | {% include 'ega/_field_snippet.html' %} 55 | {% endfor %} 56 | 57 | 58 |
59 | 60 | {% endblock %} 61 | 62 | {% block extra_body %} 63 | 76 | {% endblock %} 77 | -------------------------------------------------------------------------------- /ega/templates/ega/_predictions.html: -------------------------------------------------------------------------------- 1 | {% for prediction in history %} 2 |
3 | {% with match=prediction.match %} 4 | {% with home=match.home away=match.away %} 5 |
6 |
7 | {% if home.image %} 8 | 9 | {% endif %} 10 |
11 |
12 | 13 | 15 | {% firstof home.code home.name %} 16 | 17 |
18 |
19 | 20 | {{ match.home_goals|default_if_none:'-' }}{% if match.pk_home_goals != None %}[{{ match.pk_home_goals }}]{% endif %} 21 | 22 |
23 |
24 | 25 | {% if match.pk_away_goals != None %}[{{ match.pk_away_goals }}]{% endif %}{{ match.away_goals|default_if_none:'-' }} 26 | 27 |
28 |
29 | 30 | 32 | {% firstof away.code away.name %} 33 | 34 |
35 |
36 | {% if away.image %} 37 | 38 | {% endif %} 39 |
40 |
41 |
42 |
43 | {% if not match.finished %}En juego |{% endif %} 44 | Pronóstico: 45 | {% if prediction.home_goals != None and prediction.away_goals != None %} 46 | {{ prediction.home_goals }} - {{ prediction.away_goals }} 47 | {% if prediction.penalties_home %}[{{ match.home.code }}] 48 | {% elif prediction.penalties_away %}[{{ match.away.code }}] 49 | {% endif %} 50 | {% if match.finished and prediction.score >= 0 %}({{ prediction.score }} punto{{ prediction.score|pluralize }}){% endif %} 51 | {% endif %} 52 | | Detalles 53 |
54 |
55 | {% endwith %} 56 | {% endwith %} 57 |
58 | {% endfor %} 59 | -------------------------------------------------------------------------------- /ega/templates/ega/ranking.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block extra-head %} 5 | 11 | {% endblock %} 12 | 13 | {% block content-title %}Posiciones{% if round %} | Fecha {{ round }}{% endif %}{% endblock %} 14 | 15 | {% block content %} 16 | 17 |
18 |
19 | {% include 'ega/_user_stats.html' %} 20 |
21 |
22 | 23 |
24 |
25 | 33 |
34 | 35 | 45 |
46 |
47 | 48 |
49 |
50 | {% include 'ega/_ranking_table.html' with delta=ranking.start_index score_details=1 %} 51 | 52 |
53 |
    54 | {% if ranking.has_previous %} 55 |
  • «
  • 56 | {% endif %} 57 | {% if ranking.paginator.num_pages > 1 %} 58 | {% for p in ranking.paginator.page_range %} 59 |
  • 60 | {{ p }} 61 |
  • 62 | {% endfor %} 63 | {% endif %} 64 | {% if ranking.has_next %} 65 |
  • »
  • 66 | {% endif %} 67 |
68 |
69 | 70 |
71 | 72 | 86 |
87 | {% endblock %} 88 | -------------------------------------------------------------------------------- /ega/templates/ega/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load ega_tags %} 3 | {% load news_tags %} 4 | {% load staticfiles %} 5 | 6 | {% block content-title %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 | 12 | {% include 'ega/_user_stats.html' %} 13 | 14 |
15 | {% if champion_form %} 16 |

Pronosticá el campeón

17 | 18 |
19 |
20 | {% csrf_token %} 21 |
{{ champion_form.team }}
22 | 23 | {% if champion_form.instance.team %} 24 |
Tu candidato actual es {{ champion_form.instance.team.name }} 25 | {% endif %} 26 |
Última actualizacion: {{ champion_form.instance.last_updated }} 27 |
Acertando sin modificar tu pronóstico antes del comienzo del campeonato, 8 puntos; cambiándolo durante la primera fase, 5 puntos; antes de la semifinal, 2 puntos. 28 |
29 |
30 | 31 |
32 | {% champion_predictions_chart tournament as chart %} 33 | 34 |
35 | 36 | {% elif tournament.slug == 'uruguay-clausura-2017' %} 37 |

Noticias Ovación

38 | {% latest_news source='ovacion' latest=3 %} 39 | {% else %} 40 |

Noticias Olé

41 | {% latest_news source='ole' latest=3 %} 42 | {% endif %} 43 |
44 |
45 | 46 |
47 | {% if matches %} 48 |

49 | Próximos partidos 50 |

51 | {% include 'ega/_next_matches.html' %} 52 |

{% if pending > 0 %}completar {% endif %}mis pronósticos

53 | {% endif %} 54 | 55 | {% if history %} 56 |

57 | Últimos resultados 58 |

59 | {% include 'ega/_predictions.html' with use_code=1 %} 60 |

ver historial

61 | {% endif %} 62 |
63 | 64 |
65 | {% if top_ranking %} 66 |

67 | Posiciones 68 |

69 | {% include 'ega/_ranking_table.html' with ranking=top_ranking delta=1 %} 70 |

ver tabla completa 71 | {% if current_round %}| 72 | ver fecha {{ current_round }}{% endif %}

73 | {% endif %} 74 |
75 | 76 |
77 | 78 | {% endblock %} -------------------------------------------------------------------------------- /other_migrations/socialaccount/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | import allauth.socialaccount.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('sites', '0001_initial'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='SocialAccount', 19 | fields=[ 20 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 21 | ('provider', models.CharField(max_length=30, verbose_name='provider')), 22 | ('uid', models.CharField(max_length=255, verbose_name='uid')), 23 | ('last_login', models.DateTimeField(auto_now=True, verbose_name='last login')), 24 | ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')), 25 | ('extra_data', allauth.socialaccount.fields.JSONField(default='{}', verbose_name='extra data')), 26 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), 27 | ], 28 | options={ 29 | 'verbose_name': 'social account', 30 | 'verbose_name_plural': 'social accounts', 31 | }, 32 | ), 33 | migrations.CreateModel( 34 | name='SocialApp', 35 | fields=[ 36 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 37 | ('provider', models.CharField(max_length=30, verbose_name='provider')), 38 | ('name', models.CharField(max_length=40, verbose_name='name')), 39 | ('client_id', models.CharField(help_text='App ID, or consumer key', max_length=100, verbose_name='client id')), 40 | ('secret', models.CharField(help_text='API secret, client secret, or consumer secret', max_length=100, verbose_name='secret key')), 41 | ('key', models.CharField(help_text='Key', max_length=100, verbose_name='key', blank=True)), 42 | ('sites', models.ManyToManyField(to='sites.Site', blank=True)), 43 | ], 44 | options={ 45 | 'verbose_name': 'social application', 46 | 'verbose_name_plural': 'social applications', 47 | }, 48 | ), 49 | migrations.CreateModel( 50 | name='SocialToken', 51 | fields=[ 52 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 53 | ('token', models.TextField(help_text='"oauth_token" (OAuth1) or access token (OAuth2)', verbose_name='social account')), 54 | ('token_secret', models.TextField(help_text='"oauth_token_secret" (OAuth1) or refresh token (OAuth2)', verbose_name='token secret', blank=True)), 55 | ('expires_at', models.DateTimeField(null=True, verbose_name='expires at', blank=True)), 56 | ('account', models.ForeignKey(to='socialaccount.SocialAccount', on_delete=models.CASCADE)), 57 | ('app', models.ForeignKey(to='socialaccount.SocialApp', on_delete=models.CASCADE)), 58 | ], 59 | options={ 60 | 'verbose_name': 'social application token', 61 | 'verbose_name_plural': 'social application tokens', 62 | }, 63 | ), 64 | migrations.AlterUniqueTogether( 65 | name='socialtoken', 66 | unique_together=set([('app', 'account')]), 67 | ), 68 | migrations.AlterUniqueTogether( 69 | name='socialaccount', 70 | unique_together=set([('provider', 'uid')]), 71 | ), 72 | ] 73 | -------------------------------------------------------------------------------- /ega/templates/ega/stats.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block extra-head %} 5 | 11 | {% endblock %} 12 | 13 | {% block content-title %}Estadísticas{% endblock %} 14 | 15 | {% block content %} 16 | 17 |
18 |
19 | 20 | {% regroup ranking by zone as ranking_by_zone %} 21 | 22 | {% for zone in ranking_by_zone %} 23 |

{{ zone.grouper }}

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {% for row in zone.list %} 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | {% endfor %} 61 | 62 |
#Puntos
{{ forloop.counter }}{% if row.team.image %} 46 | 47 | {% endif %} 48 | {{ row.team }}{{ row.points }}
63 | {% endfor %} 64 | 65 |
66 | 67 |
68 |

Top 5 - Más repetidos

69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {% for result, prediction in top_5 %} 78 | 79 | 81 | 82 | 84 | 85 | {% endfor %} 86 | 87 |
ResultadosPronósticos
{{ result.0.0 }} - {{ result.0.1 }} 80 | ({{ result.1 }}){{ prediction.0.0 }} - {{ prediction.0.1 }} 83 | ({{ prediction.1 }})
88 | 89 |

90 |
91 |
92 | {% if no_loses %} 93 |
No perdi{{ no_loses|pluralize:'ó,eron' }} todavía
94 |
{{ no_loses|join:', '}}
95 | {% else %} 96 |
No quedan invictos en el torneo
97 | {% endif %} 98 | {% if no_ties %} 99 |
No empat{{ no_ties|pluralize:'ó,aron' }} todavía
100 |
{{ no_ties|join:', '}}
101 | {% endif %} 102 | {% if no_wins %} 103 |
No gan{{ no_wins|pluralize:'ó,aron' }} todavía
104 |
{{ no_wins|join:', '}}
105 | {% endif %} 106 |
107 |
108 | 109 |
110 |
111 | {% endblock %} 112 | -------------------------------------------------------------------------------- /ega/tests/test_views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | import os 6 | 7 | from django.contrib.sites.models import Site 8 | from django.test import TestCase 9 | from django.urls import reverse 10 | from allauth.socialaccount.models import SocialApp 11 | 12 | from ega.constants import DEFAULT_TOURNAMENT 13 | from ega.models import EgaUser, Tournament 14 | 15 | 16 | class BaseTestCase(TestCase): 17 | 18 | def setUp(self): 19 | super(BaseTestCase, self).setUp() 20 | app = SocialApp.objects.create( 21 | name='facebook', provider='facebook', 22 | client_id='lorem', secret='ipsum') 23 | app.sites.add(Site.objects.get_current()) 24 | Tournament.objects.create(slug=DEFAULT_TOURNAMENT, published=True) 25 | Tournament.objects.create(slug='other', published=True) 26 | 27 | 28 | class LoginTestCase(BaseTestCase): 29 | 30 | url = reverse('account_login') 31 | bad_login = ( 32 | 'El usuario y/o la contraseña que especificaste no son correctos.') 33 | 34 | def setUp(self): 35 | super(LoginTestCase, self).setUp() 36 | self.user = EgaUser.objects.create_user( 37 | username='test', password='12345678') 38 | 39 | def login(self, username, password, **kwargs): 40 | response = self.client.post( 41 | self.url, data=dict(login=username, password=password), **kwargs) 42 | return response 43 | 44 | def test_non_existing_user(self): 45 | response = self.login('foo', 'password') 46 | self.assertContains(response, self.bad_login) 47 | self.assertFormError(response, 'form', None, self.bad_login) 48 | 49 | def test_bad_credentials(self): 50 | response = self.login(self.user.username, 'password') 51 | self.assertContains(response, self.bad_login) 52 | self.assertFormError(response, 'form', None, self.bad_login) 53 | 54 | def test_success(self): 55 | response = self.login(self.user.username, '12345678', follow=True) 56 | self.assertRedirects(response, reverse('meta-home')) 57 | self.assertEqual( 58 | [m.message for m in response.context['messages']], 59 | ['Has iniciado sesión exitosamente como %s.' % self.user.username]) 60 | 61 | 62 | class SignUpTestCase(BaseTestCase): 63 | 64 | url = reverse('account_signup') 65 | bad_username = 'Ya existe un usuario con este nombre.' 66 | bad_email = ( 67 | 'Un usuario ya fue registrado con esta dirección de correo ' 68 | 'electrónico.') 69 | good_pw = '1234567U' 70 | 71 | def setUp(self): 72 | super(SignUpTestCase, self).setUp() 73 | self.user = EgaUser.objects.create_user( 74 | username='test', email='test@example.com', password=self.good_pw) 75 | 76 | def signup(self, username, email, password, **kwargs): 77 | data = { 78 | 'username': username, 'email': email, 'password1': password, 79 | 'password2': password, 'g-recaptcha-response': 'PASSED'} 80 | response = self.client.post(self.url, data=data, **kwargs) 81 | return response 82 | 83 | def test_existing_username(self): 84 | response = self.signup( 85 | self.user.username, 'foo@example.com', self.good_pw) 86 | self.assertFormError(response, 'form', 'username', self.bad_username) 87 | self.assertContains(response, self.bad_username) 88 | 89 | def test_existing_email(self): 90 | response = self.signup('zaraza', self.user.email, self.good_pw) 91 | self.assertFormError(response, 'form', 'email', self.bad_email) 92 | self.assertContains(response, self.bad_email) 93 | 94 | def test_success(self): 95 | os.environ['NORECAPTCHA_TESTING'] = 'True' 96 | self.addCleanup(os.environ.pop, 'NORECAPTCHA_TESTING') 97 | 98 | new = 'foobar' 99 | response = self.signup( 100 | new, 'foo@example.com', self.good_pw, follow=True) 101 | self.assertRedirects(response, reverse('meta-home')) 102 | self.assertEqual( 103 | sorted(m.message for m in response.context['messages']), 104 | ['Correo electrónico enviado a foo@example.com.', 105 | 'Has iniciado sesión exitosamente como %s.' % new]) 106 | self.assertEqual(EgaUser.objects.filter(username=new).count(), 1) 107 | -------------------------------------------------------------------------------- /ega/management/commands/update_matches.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | import demiurge 6 | 7 | from django.core.management.base import BaseCommand 8 | from django.utils.timezone import get_default_timezone, make_aware 9 | 10 | from ega.models import Match, Team, Tournament 11 | 12 | 13 | TEAM_MAPPING = { 14 | 'Defensa': 'Defensa y Justicia', 15 | 'Newell`s': "Newell's Old Boys", 16 | 'Racing Club': 'Racing', 17 | 'Talleres': 'Talleres (Cba)', 18 | 'Atl. Rafaela': 'Atlético Rafaela', 19 | 'Argentinos': 'Argentinos Juniors', 20 | 'S. Martín SJ': 'San Martín (SJ)', 21 | 'Atl. Tucumán': 'Atlético Tucumán', 22 | 'Boca': 'Boca Juniors', 23 | 'River': 'River Plate', 24 | 'R. Central': 'Rosario Central', 25 | } 26 | 27 | 28 | class MatchData(demiurge.Item): 29 | home = demiurge.TextField(selector='div.local div.equipo') 30 | away = demiurge.TextField(selector='div.visitante div.equipo') 31 | home_goals = demiurge.TextField(selector='div.local div.resultado') 32 | away_goals = demiurge.TextField(selector='div.visitante div.resultado') 33 | status = demiurge.TextField(selector='div.detalles div.estado') 34 | 35 | _date = demiurge.TextField(selector='div.detalles div.dia') 36 | _time = demiurge.TextField(selector='div.detalles div.hora') 37 | 38 | class Meta: 39 | selector = 'div.mc-matchContainer' 40 | encoding = 'utf-8' 41 | base_url = ('http://staticmd1.lavozdelinterior.com.ar/sites/default/' 42 | 'files/Datafactory/html/v3/htmlCenter/data/deportes/' 43 | 'futbol/primeraa/pages/es/fixture.html') 44 | 45 | @property 46 | def is_finished(self): 47 | return self.status.lower() == 'finalizado' 48 | 49 | @property 50 | def is_suspended(self): 51 | return self.status.lower() == 'suspendido' 52 | 53 | @property 54 | def in_progress(self): 55 | return self.status.lower() == 'en juego' 56 | 57 | @property 58 | def when(self): 59 | if self._time is None: 60 | return None 61 | 62 | if self._time.startswith('-'): 63 | match_time = "00:00" 64 | else: 65 | match_time = self._time[:5] 66 | 67 | date_and_time = "%s %s" % (self._date, match_time) 68 | value = datetime.strptime(date_and_time, "%d-%m-%Y %H:%M") 69 | return make_aware(value, get_default_timezone()) 70 | 71 | 72 | class Command(BaseCommand): 73 | help = 'Import match details' 74 | 75 | def handle(self, *args, **options): 76 | try: 77 | matches = MatchData.all() 78 | except: 79 | # skip if couldn't get data 80 | return 81 | 82 | tournament = Tournament.objects.get(slug='superliga') 83 | 84 | for i, entry in enumerate(matches): 85 | when = entry.when 86 | changed = False 87 | 88 | home = TEAM_MAPPING.get(entry.home, entry.home) 89 | away = TEAM_MAPPING.get(entry.away, entry.away) 90 | 91 | home_team = Team.objects.get( 92 | name=home, tournament=tournament) 93 | away_team = Team.objects.get( 94 | name=away, tournament=tournament) 95 | 96 | match, created = Match.objects.get_or_create( 97 | tournament=tournament, home=home_team, away=away_team) 98 | 99 | if created: 100 | self.stdout.write(u'Match created: %s\n' % str(match)) 101 | 102 | if not match.suspended and entry.is_suspended: 103 | match.suspended = True 104 | changed = True 105 | 106 | if when != match.when and not match.suspended and when.hour != 0: 107 | round = (i // 14 + 1) 108 | match.when = when 109 | match.description = 'Fecha %d' % round 110 | match.round = str(round) 111 | changed = True 112 | 113 | if (not match.finished and entry.home_goals != '' and 114 | entry.away_goals != ''): 115 | changed = True 116 | match.home_goals = entry.home_goals 117 | match.away_goals = entry.away_goals 118 | match.finished = entry.is_finished 119 | if match.finished: 120 | self.stdout.write(u'Updated result: %s: %s - %s\n' % ( 121 | match, entry.home_goals, entry.away_goals)) 122 | 123 | if changed: 124 | match.save() 125 | -------------------------------------------------------------------------------- /ega/templates/ega/meta_home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load ega_tags %} 3 | {% load news_tags %} 4 | {% load staticfiles %} 5 | 6 | {% block content-title %}{% endblock %} 7 | 8 | {% block content %} 9 | 10 |
11 |
12 | 16 |
17 | 18 |
19 |

Próximos partidos

20 | 21 | {% regroup next_matches by match.tournament as match_list %} 22 | 23 | 29 | 30 |
31 | {% for tournament in match_list %} 32 |
33 | 34 | {% for row in tournament.list|slice:":20" %} 35 |
36 |
{{ row.match.when|date:"F j, P" }}
37 |
38 | {% firstof row.match.home.name row.match.home_placeholder %} 39 |
40 |
41 | {% firstof row.match.away.name row.match.away_placeholder %} 42 |
43 |
44 | 45 | {% if row.prediction %} 46 | 47 | {% with prediction=row.prediction %} 48 | ({{ prediction.home_goals }}-{{ prediction.away_goals }}{% if prediction.penalties_home %}, pasa {{ row.match.home.code }}{% elif prediction.penalties_away %}, pasa {{ row.match.away.code }}{% endif %}) 49 | {% endwith %} 50 | 51 | {% endif %} 52 |
53 |
54 | {% empty %} 55 |

No hay partidos por jugar

56 | {% endfor %} 57 | 58 |
59 | {% endfor %} 60 |
61 | 62 |
63 | 64 | {% for t in available_tournaments %} 65 |

66 | {{ t.name }} 67 | ir 68 |

69 |
70 | {% with top3=t.ranking|slice:":3" %} 71 | {% if top3 %} 72 |
Posiciones 73 | 74 |
75 |
76 | {% for row in top3 %} 77 | {{ forloop.counter }}. 78 | {{ row.username }} ({{ row.total }}pts){% if not forloop.last %} |{% endif %} 79 | {% endfor %} 80 |
81 | {% endif %} 82 | {% endwith %} 83 | 84 | {% get_user_stats request.user t as stats %} 85 | {% if stats.count > 0 %} 86 |
Performance
87 |
88 | {{ stats.score }} punto{{ stats.score|pluralize }} 89 | ({{ stats.count }} partido{{ stats.count|pluralize }} jugado{{ stats.count|pluralize }}, 90 | {{ stats.winners }} acierto{{ stats.winners|pluralize }}, 91 | {{ stats.exacts }} exacto{{ stats.exacts|pluralize }}) 92 |
93 | {% endif %} 94 | 95 |
96 | {% endfor %} 97 | 98 |
99 | 100 |
Torneos finalizados
101 |

102 | {% for t in past_tournaments %} 103 | {{ t.name }}{% if not forloop.last %} |{% endif %} 104 | {% endfor %} 105 |

106 | 107 |
108 |
109 | {% endblock %} 110 | -------------------------------------------------------------------------------- /ega/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load socialaccount %} 6 | {% load staticfiles %} 7 | 8 | {% block navbar-brand-logo %} 9 | 10 | el Ega 11 | {% endblock %} 12 | 13 | {% block content-title %}{% endblock %} 14 | 15 | {% block content %} 16 | 17 |
18 | 21 | 22 |
23 |

el Ega

24 |

El juego de pronósticos deportivos

25 | 26 |

Visitá nuestro blog, seguinos en twitter!

27 | 28 |
29 |
Pronosticá
30 |
Intentá acertar los resultados de los partidos y ganá puntos de acuerdo a la precisión de tus pronósticos.
31 | 32 |
Competí
33 |
No sólo con todos los usuarios del sitio, también podés armar una liga propia entre amigos y/o compañeros de trabajo.
34 | 35 |
Jugá!
36 |
Si hiciste al menos un pronóstico en una edición anterior, tu usuario todavía es válido! 37 | Si no, es un buen momento para registrarte.
38 |
39 | 40 |
41 | 42 |
43 | {% get_providers as socialaccount_providers %} 44 | {% if socialaccount_providers %} 45 | 65 | {% endif %} 66 | 67 | 96 |
97 | 98 |
99 | 100 |
101 |
102 | 103 |

Pulpo Paul 104 |
"Oráculos éramos los de antes, no me vengan con redes neuronales y machine learning."

105 |
106 | 107 |
108 | 109 |

Diego M. 110 |
"Eh... eehhhh... ehhh... ga."

111 |
112 | 113 |
114 | 115 |

Gonzalo H. 116 |
"Nunca me pierdo una edición de el gran DT."

117 |
118 | 119 |
120 | {% endblock %} 121 | -------------------------------------------------------------------------------- /ega/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import forms 4 | from django.core.exceptions import ValidationError 5 | from django.core.validators import validate_email 6 | from nocaptcha_recaptcha.fields import NoReCaptchaField 7 | 8 | from ega.constants import EMAILS_PLACEHOLDER 9 | from ega.models import ChampionPrediction, EgaUser, League, Prediction, Team 10 | 11 | 12 | class PredictionForm(forms.ModelForm): 13 | GOAL_CHOICES = [('', '-')] + [(i, i) for i in range(20)] 14 | 15 | home_goals = forms.ChoiceField( 16 | choices=GOAL_CHOICES, required=False, 17 | widget=forms.Select(attrs={'class': 'form-control input-lg'})) 18 | away_goals = forms.ChoiceField( 19 | choices=GOAL_CHOICES, required=False, 20 | widget=forms.Select(attrs={'class': 'form-control input-lg'})) 21 | penalties = forms.ChoiceField( 22 | choices=[], required=False, widget=forms.RadioSelect()) 23 | 24 | def __init__(self, *args, **kwargs): 25 | super(PredictionForm, self).__init__(*args, **kwargs) 26 | self.expired = False 27 | if self.instance.match.knockout: 28 | match = self.instance.match 29 | home = match.home.name if match.home else match.home_placeholder 30 | away = match.away.name if match.away else match.away_placeholder 31 | self.fields['penalties'].choices = [('L', home), ('V', away)] 32 | 33 | def _clean_goals(self, field_name): 34 | goals = self.cleaned_data.get(field_name) 35 | if not goals: 36 | goals = None 37 | return goals 38 | 39 | def clean_home_goals(self): 40 | return self._clean_goals('home_goals') 41 | 42 | def clean_away_goals(self): 43 | return self._clean_goals('away_goals') 44 | 45 | def clean(self): 46 | cleaned_data = super(PredictionForm, self).clean() 47 | home_goals = cleaned_data.get("home_goals") 48 | away_goals = cleaned_data.get("away_goals") 49 | 50 | msg = "Pronóstico incompleto." 51 | if (home_goals and not away_goals): 52 | raise forms.ValidationError(msg) 53 | if (not home_goals and away_goals): 54 | raise forms.ValidationError(msg) 55 | 56 | penalties = cleaned_data.get('penalties') 57 | if penalties and home_goals != away_goals: 58 | msg = "Penales se puede pronosticar sólo en caso de empate." 59 | raise forms.ValidationError(msg) 60 | 61 | return cleaned_data 62 | 63 | def save(self, *args, **kwargs): 64 | match = self.instance.match 65 | if not match.is_expired and self.has_changed(): 66 | return super(PredictionForm, self).save(*args, **kwargs) 67 | elif match.is_expired: 68 | self.expired = True 69 | return None 70 | 71 | class Meta: 72 | model = Prediction 73 | fields = ('home_goals', 'away_goals', 'penalties') 74 | 75 | 76 | class ChampionPredictionForm(forms.ModelForm): 77 | team = forms.ModelChoiceField( 78 | queryset=Team.objects.none(), 79 | widget=forms.Select(attrs={'class': 'form-control'})) 80 | 81 | def __init__(self, *args, **kwargs): 82 | super(ChampionPredictionForm, self).__init__(*args, **kwargs) 83 | tournament_teams = self.instance.tournament.teams.order_by('name') 84 | self.fields['team'].queryset = tournament_teams 85 | 86 | class Meta: 87 | model = ChampionPrediction 88 | fields = ('team',) 89 | 90 | 91 | class InviteFriendsForm(forms.Form): 92 | 93 | emails = forms.CharField( 94 | widget=forms.Textarea( 95 | attrs={'rows': 1, 'class': 'form-control', 96 | 'placeholder': EMAILS_PLACEHOLDER})) 97 | subject = forms.CharField( 98 | widget=forms.TextInput(attrs={'class': 'form-control'}), 99 | ) 100 | body = forms.CharField( 101 | widget=forms.Textarea(attrs={'rows': 10, 'class': 'form-control'}), 102 | ) 103 | 104 | def clean_emails(self): 105 | emails = [] 106 | for email in self.cleaned_data['emails'].split(','): 107 | emails.extend(e.strip() for e in email.strip().split() if e) 108 | 109 | errors = [] 110 | for email in emails: 111 | try: 112 | validate_email(email) 113 | except ValidationError: 114 | errors.append(email) 115 | 116 | if len(errors) == 1: 117 | raise ValidationError( 118 | 'El email "%s" no es una dirección válida.' % errors[0]) 119 | elif len(errors) > 1: 120 | raise ValidationError( 121 | 'Los emails "%s" no son direcciones válidas' % ', '.join( 122 | errors)) 123 | 124 | return list(set(emails)) 125 | 126 | def invite(self, sender): 127 | emails = self.cleaned_data['emails'] 128 | subject = self.cleaned_data['subject'] 129 | body = self.cleaned_data['body'] 130 | return sender.invite_friends(emails, subject, body) 131 | 132 | 133 | class LeagueForm(forms.ModelForm): 134 | 135 | name = forms.CharField( 136 | widget=forms.TextInput(attrs={'class': 'form-control'}), 137 | label='Nombre', 138 | ) 139 | 140 | class Meta: 141 | model = League 142 | fields = ('name',) 143 | 144 | 145 | class EgaUserForm(forms.ModelForm): 146 | 147 | class Meta: 148 | model = EgaUser 149 | fields = ('username', 'first_name', 'last_name', 'avatar') 150 | 151 | 152 | class CustomSignupForm(forms.Form): 153 | 154 | captcha = NoReCaptchaField(label='') 155 | 156 | def signup(self, request, user): 157 | """ Required, or else it throws deprecation warnings """ 158 | pass 159 | -------------------------------------------------------------------------------- /fenics/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for fenics project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | 16 | from django.contrib.messages import constants as message_constants 17 | 18 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 19 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = '+jfjs%6uek56jsaug4p^^spf+d#8#9+j5r^9+2@een07argb81' 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = True 30 | 31 | ALLOWED_HOSTS = [] 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.flatpages', 41 | 'django.contrib.humanize', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.sites', 45 | 'django.contrib.staticfiles', 46 | 'ega', 47 | 'news', 48 | 'allauth', 49 | 'nocaptcha_recaptcha', 50 | 'allauth.account', 51 | 'allauth.socialaccount', 52 | # 'allauth.socialaccount.providers.facebook', 53 | # 'allauth.socialaccount.providers.google', 54 | # 'allauth.socialaccount.providers.twitter', 55 | ] 56 | 57 | MIDDLEWARE = [ 58 | 'django.middleware.security.SecurityMiddleware', 59 | 'django.contrib.sessions.middleware.SessionMiddleware', 60 | 'django.middleware.common.CommonMiddleware', 61 | 'django.middleware.csrf.CsrfViewMiddleware', 62 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 63 | 'django.contrib.messages.middleware.MessageMiddleware', 64 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 65 | 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 66 | ] 67 | 68 | ROOT_URLCONF = 'fenics.urls' 69 | 70 | TEMPLATES = [ 71 | { 72 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 73 | 'DIRS': [ 74 | os.path.join(BASE_DIR, 'templates'), 75 | ], 76 | 'APP_DIRS': True, 77 | 'OPTIONS': { 78 | 'context_processors': [ 79 | 'django.template.context_processors.debug', 80 | # `allauth` needs this from django 81 | 'django.template.context_processors.request', 82 | 'django.template.context_processors.i18n', 83 | 'django.template.context_processors.media', 84 | 'django.template.context_processors.static', 85 | 'django.template.context_processors.tz', 86 | 'django.contrib.auth.context_processors.auth', 87 | 'django.contrib.messages.context_processors.messages', 88 | 'fenics.context_processors.available_tournaments', 89 | 'fenics.context_processors.disqus_shortname', 90 | ], 91 | }, 92 | }, 93 | ] 94 | 95 | WSGI_APPLICATION = 'fenics.wsgi.application' 96 | 97 | 98 | # Database 99 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 100 | 101 | DATABASES = { 102 | 'default': { 103 | 'ENGINE': 'django.db.backends.sqlite3', 104 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 105 | } 106 | } 107 | 108 | 109 | # Password validation 110 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 111 | 112 | AUTH_PASSWORD_VALIDATORS = [ 113 | { 114 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 115 | }, 116 | { 117 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 118 | }, 119 | { 120 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 121 | }, 122 | { 123 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 124 | }, 125 | ] 126 | 127 | 128 | # Internationalization 129 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 130 | 131 | LANGUAGE_CODE = 'es' 132 | 133 | TIME_ZONE = 'America/Argentina/Buenos_Aires' 134 | 135 | USE_I18N = True 136 | 137 | USE_L10N = True 138 | 139 | USE_TZ = True 140 | 141 | 142 | # Static files (CSS, JavaScript, Images) 143 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 144 | 145 | STATIC_URL = '/static/' 146 | 147 | STATICFILES_DIRS = ( 148 | os.path.join(BASE_DIR, 'static'), 149 | ) 150 | 151 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 152 | MEDIA_URL = '/media/' 153 | 154 | AUTHENTICATION_BACKENDS = ( 155 | 'django.contrib.auth.backends.ModelBackend', 156 | # `allauth` specific authentication methods, such as login by e-mail 157 | 'allauth.account.auth_backends.AuthenticationBackend', 158 | ) 159 | 160 | ADMINS = [ 161 | ('Natalia', 'natalia@gmail.com'), 162 | ('Matias', 'matias@gmail.com'), 163 | ] 164 | AUTH_USER_MODEL = 'ega.EgaUser' 165 | ACCOUNT_EMAIL_REQUIRED = True 166 | ACCOUNT_SESSION_REMEMBER = True 167 | SOCIALACCOUNT_QUERY_EMAIL = True 168 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 169 | LOGIN_REDIRECT_URL = 'meta-home' 170 | SITE_ID = 1 171 | 172 | ACCOUNT_SIGNUP_FORM_CLASS = 'ega.forms.CustomSignupForm' 173 | 174 | MESSAGE_TAGS = {message_constants.ERROR: 'danger'} 175 | 176 | 177 | EL_EGA_ADMIN = 'admin@el-ega.com.ar' 178 | EL_EGA_NO_REPLY = 'noreply@el-ega.com.ar' 179 | 180 | DISQUS_SHORTNAME = 'elega-staging' 181 | NEWS_FEEDS = ( 182 | ('ole', 'http://www.ole.com.ar/rss/futbol-primera/'), 183 | ('ovacion', 'http://www.ovaciondigital.com.uy/rss/ovacion/futbol'), 184 | ('ega', 'http://blog.el-ega.com.ar/rss'), 185 | ) 186 | 187 | HONEYPOT_FIELD_NAME = 'user-secret' 188 | 189 | MIGRATION_MODULES = { 190 | 'socialaccount': 'other_migrations.socialaccount', 191 | 'account': 'other_migrations.account', 192 | } 193 | 194 | NORECAPTCHA_SITE_KEY = '' 195 | NORECAPTCHA_SECRET_KEY = '' 196 | 197 | 198 | try: 199 | from .local_settings import * 200 | except ImportError: 201 | pass 202 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load i18n %} 3 | {% load ega_tags %} 4 | 5 | 6 | 7 | 8 | 9 | {% block html-title %}el Ega{% endblock %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | {% block extra-head %} 26 | {% endblock %} 27 | 28 | 29 | 30 | 31 | 32 | 100 | 101 |
102 |

103 | {% block content-title %}el Ega{% endblock %} 104 |

105 | 106 |
107 | {% for message in messages %} 108 |
109 | {{ message }} 110 |
111 | {% endfor %} 112 |
113 | 114 |
115 | {% block content %} 116 | {% endblock %} 117 |
118 |
119 | 120 | 131 | 132 | {% block analytics %} 133 | 143 | {% endblock %} 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | padding-top: 90px; 8 | /* Margin bottom by footer height */ 9 | margin-bottom: 90px; 10 | } 11 | 12 | dt { 13 | margin-top: 10px; 14 | } 15 | 16 | .login-logo { 17 | padding-top: 30px; 18 | padding-bottom: 30px; 19 | } 20 | 21 | .testimonials { 22 | margin-top: 100px; 23 | } 24 | 25 | .section { 26 | margin-top: 25px; 27 | } 28 | 29 | .navbar-lower { 30 | background-color: white; 31 | top: 50px; 32 | z-index: 500; 33 | border: none; 34 | margin-top: 0px; 35 | margin-bottom: 0px; 36 | } 37 | 38 | .starred { 39 | background: url('/static/images/star.jpg') no-repeat center; 40 | } 41 | 42 | .starred-row { 43 | background: url('/static/images/star_alpha.png') no-repeat center center/45px 45px; 44 | } 45 | 46 | .starred-font { 47 | color: #a17f1a; 48 | } 49 | 50 | .starred-xs { 51 | margin-top: -10px; 52 | margin-bottom: 10px; 53 | } 54 | 55 | .title { 56 | border-bottom: 1px solid #EEE; 57 | margin-bottom: 20px; 58 | margin-top: 10px; 59 | } 60 | 61 | .history-section { 62 | margin-top: 50px; 63 | } 64 | 65 | .title-action { 66 | text-align: right; 67 | margin-top: 10px; 68 | } 69 | 70 | .title-main-action { 71 | text-align: right; 72 | margin-top: 10px; 73 | font-size: larger; 74 | } 75 | 76 | .tournament-icon { 77 | max-height: 20px; 78 | } 79 | 80 | .badge-pending { 81 | background-color: #3c463e; 82 | } 83 | 84 | .predicted-score { 85 | color: grey; 86 | } 87 | 88 | .nav-logo { 89 | max-height: 50px; 90 | } 91 | 92 | .user-stats { 93 | width: 100%; 94 | } 95 | 96 | .homepage-match-row { 97 | margin-top: 10px; 98 | } 99 | 100 | .providers-login { 101 | margin: 25px; 102 | } 103 | 104 | .login-form { 105 | max-width: 175px; 106 | } 107 | 108 | table.ranking tbody tr td { 109 | vertical-align: middle; 110 | word-break: break-all; 111 | } 112 | 113 | table.ranking { 114 | font-size: 1.1em; 115 | } 116 | 117 | .ranking .avatar { 118 | max-width: 50px; 119 | max-height: 50px; 120 | } 121 | 122 | .ranking .score { 123 | padding-right: 25px; 124 | } 125 | 126 | .user-avatar { 127 | max-width: 75px; 128 | max-height: 75px; 129 | } 130 | 131 | .user-avatar-big { 132 | max-width: 130px; 133 | max-height: 130px; 134 | } 135 | 136 | .navbar-matches { 137 | background-color: rgba(248,248,248,0.8); 138 | border: 0; 139 | text-align: center; 140 | padding: 5px; 141 | } 142 | 143 | .navbar-matches.affix-top { 144 | position: fixed; 145 | bottom: 25px; 146 | right: 0px; 147 | width: 100%; 148 | z-index: 9999 !important; 149 | } 150 | 151 | .navbar-matches.affix { 152 | position: fixed; 153 | bottom: 25px; 154 | right: 0px; 155 | width: 100%; 156 | z-index: 9999 !important; 157 | } 158 | 159 | .team-image { 160 | max-width: 65px; 161 | } 162 | 163 | .team-image-xsmall { 164 | max-width: 35px; 165 | } 166 | 167 | .team-image-small { 168 | max-width: 65px; 169 | margin-top: -20px; 170 | } 171 | 172 | .team-panel-name { 173 | margin-top: 15px; 174 | } 175 | 176 | .goals-input { 177 | padding-bottom: 10px; 178 | } 179 | 180 | .penalties-input { 181 | margin-bottom: 20px; 182 | } 183 | 184 | .match-metadata { 185 | position: relative; 186 | top: -50px; 187 | } 188 | 189 | .home-stats { 190 | color: #333; 191 | text-align: left; 192 | } 193 | 194 | .home-stats .label { 195 | opacity: 0.7; 196 | } 197 | 198 | 199 | .home-image { 200 | float: right; 201 | } 202 | 203 | .away-stats { 204 | color: #333; 205 | text-align: right; 206 | } 207 | 208 | .away-stats .label { 209 | opacity: 0.7; 210 | } 211 | 212 | .away-image { 213 | float: left; 214 | } 215 | 216 | .match-goals { 217 | font-size: 40px; 218 | } 219 | 220 | .match-status { 221 | margin-bottom: 10px; 222 | } 223 | 224 | #match-status { 225 | margin-right: 10px; 226 | } 227 | 228 | p.home-event { 229 | padding-left: 20px; 230 | background: url('/static/images/whistle_icon.png') no-repeat; 231 | } 232 | 233 | p.away-event { 234 | padding-right: 20px; 235 | background: url('/static/images/whistle_icon.png') no-repeat right; 236 | } 237 | 238 | 239 | p.home-substitution-in { 240 | background: url('/static/images/in_icon.png') no-repeat; 241 | } 242 | 243 | p.home-substitution-out { 244 | background: url('/static/images/out_icon.png') no-repeat; 245 | } 246 | 247 | p.home-yellow-card { 248 | background: url('/static/images/yellow_card_icon.gif') no-repeat; 249 | } 250 | 251 | p.home-second-yellow-card { 252 | background: url('/static/images/red_card_icon.gif') no-repeat; 253 | } 254 | 255 | p.home-red-card { 256 | background: url('/static/images/red_card_icon.gif') no-repeat; 257 | } 258 | 259 | p.home-goal, p.home-goal-penalty { 260 | background: url('/static/images/ball_icon.png') no-repeat; 261 | } 262 | 263 | p.away-substitution-in { 264 | background: url('/static/images/in_icon.png') no-repeat right; 265 | } 266 | 267 | p.away-substitution-out { 268 | background: url('/static/images/out_icon.png') no-repeat right; 269 | } 270 | 271 | p.away-yellow-card { 272 | background: url('/static/images/yellow_card_icon.gif') no-repeat right; 273 | } 274 | 275 | p.away-second-yellow-card { 276 | background: url('/static/images/red_card_icon.gif') no-repeat right; 277 | } 278 | 279 | p.away-red-card { 280 | background: url('/static/images/red_card_icon.gif') no-repeat right; 281 | } 282 | 283 | p.away-goal, p.away-goal-penalty { 284 | background: url('/static/images/ball_icon.png') no-repeat right; 285 | } 286 | 287 | .news-item { 288 | margin-top: -10px; 289 | margin-bottom: 25px; 290 | } 291 | 292 | table.latest-results { 293 | margin-bottom: 0px; 294 | } 295 | 296 | table.latest-results thead th { 297 | border-bottom: 1px solid #EEE; 298 | padding: 2px; 299 | } 300 | 301 | table.latest-results tbody td { 302 | border: 0px; 303 | padding: 2px; 304 | } 305 | 306 | table.latest-results tbody td.team-td { 307 | width: 40%; 308 | } 309 | 310 | table.latest-results tbody td.result-td { 311 | width: 10%; 312 | } 313 | 314 | .progress-bar { 315 | border: 0; 316 | box-shadow: none; 317 | } 318 | .progress-tie { 319 | color: #8A6D3B; 320 | background-color: #FCF8E3; 321 | } 322 | 323 | .progress-local { 324 | color: #3C763D; 325 | background-color: #DFF0D8; 326 | } 327 | 328 | .progress-away { 329 | color: #31708F; 330 | background-color: #D9EDF7; 331 | } 332 | 333 | .footer { 334 | background: url('/static/images/footer-grass.png') repeat-x; 335 | position: absolute; 336 | bottom: 0; 337 | width: 100%; 338 | /* Set the fixed height of the footer here */ 339 | height: 80px; 340 | } 341 | 342 | .footer-links { 343 | margin-top: 55px; 344 | background-color: rgba(255, 255, 255, 0.7); 345 | font-size: 1.1em; 346 | } 347 | 348 | .footer-links a { 349 | color: green; 350 | } 351 | 352 | 353 | /* Responsive customizations */ 354 | 355 | @media (max-width: 768px) { 356 | .away-stats { 357 | text-align: left; 358 | } 359 | .away-image { 360 | float: right; 361 | } 362 | .title { 363 | margin-top: 50px; 364 | } 365 | p.away-substitution-in, p.away-substitution-out, p.away-yellow-card, 366 | p.away-red-card, p.away-goal, p.away-goal-penalty { 367 | background-position: left; 368 | padding-left: 20px; 369 | } 370 | .starred { 371 | background: none; 372 | } 373 | p.home-yellow-card, p.away-yellow-card, 374 | p.home-substitution-in, p.home-substitution-out, 375 | p.away-substitution-in, p.away-substitution-out{ 376 | display: none; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /ega/templates/ega/next_matches.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | {% load ega_tags %} 4 | {% load humanize %} 5 | {% load staticfiles %} 6 | 7 | {% block extra-head %} 8 | 9 | 72 | {% endblock %} 73 | 74 | {% block content-title %}Próximos partidos{% endblock %} 75 | 76 | {% block content %} 77 | 78 | {% if formset.forms %} 79 | 80 |
81 | {% csrf_token %} 82 | {{ formset.management_form }} 83 | 84 | {% for form in formset.forms %} 85 | 86 |
87 | {% with match=form.instance.match %} 88 |
89 |
90 | {{ match.when|date:"F j, H:i" }} | 91 | {{ match.description }} 92 | {% if match.location %}{% endif %} 93 |
94 |
95 | 96 | 97 | {{ form.id }} 98 | {{ form.match }} 99 | 100 | {% with home=match.home home_placeholder=match.home_placeholder away=match.away away_placeholder=match.away_placeholder %} 101 |
102 |
103 |
104 | {% show_prediction_trends match %} 105 |
106 |
107 | {% if form.instance.starred %} 108 |
109 | 110 |
111 | {% endif %} 112 |
113 |
114 | {% include "ega/_team_details.html" with condition="home" team=home placeholder=home_placeholder stats=form.instance.home_team_stats %} 115 |
116 |
117 | {% if form.non_field_errors %} 118 |
119 |
120 | {{ form.non_field_errors|join:", " }} 121 |
122 |
123 | {% endif %} 124 |
125 |

{{ form.home_goals }}

126 |

{{ form.away_goals }}

127 |
128 | {% if match.knockout %} 129 | {% with penalties=form.penalties %} 130 |
131 |
132 | En los penales gana
133 |
134 | 137 | 140 |
141 |
142 |
143 | {% endwith %} 144 | {% endif %} 145 |
146 |
147 | {% include "ega/_team_details.html" with condition="away" team=away placeholder=away_placeholder stats=form.instance.away_team_stats %} 148 |
149 |
150 |
151 | {% endwith %} 152 | {% endwith %} 153 |
154 | {% endfor %} 155 | 156 | 160 | 161 |
162 | 163 | {% else %} 164 |

No hay partidos para jugar.

165 | {% endif %} 166 | 167 | {% endblock %} 168 | -------------------------------------------------------------------------------- /ega/templates/ega/match_details.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load ega_tags %} 3 | {% load staticfiles %} 4 | 5 | 6 | {# block extra-head #} 7 | 8 | 69 | 70 | {# endblock #} 71 | 72 | 73 | {% block content-title %}{% endblock %} 74 | 75 | {% block content %} 76 | 77 | {% with home=match.home away=match.away %} 78 |
79 |
80 |
81 | {% show_prediction_trends match %} 82 |
83 |
84 |
85 |
86 | 87 | {% if not match.finished %}{% endif %} 88 | {{ match.when|date:"F j, H:i" }} 89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {% firstof home match.home_placeholder %} 99 | {% if home.image %} 100 | 101 | {% endif %}
102 |
103 |
104 |
105 |
106 |
107 |
108 | {{ match.home_goals|default_if_none:"-" }} 109 | {% if match.pk_home_goals != None %}[{{ match.pk_home_goals }}]{% endif %} 110 |
111 |
112 |
113 |
114 |
115 | 119 |
120 |
121 |
122 |
123 | {% firstof away match.away_placeholder %} 124 | {% if away.image %} 125 | 126 | {% endif %}
127 |
128 |
129 |
130 |
131 |
132 |
133 | {{ match.away_goals|default_if_none:"-" }} 134 | {% if match.pk_away_goals != None %}[{{ match.pk_away_goals }}]{% endif %} 135 |
136 |
137 |
138 |
139 |
140 | {% endwith %} 141 | 142 | {% if match.finished %} 143 |
144 |
145 |
146 |
147 |
Aciertos exactos ({{ exacts.count }})
148 |
149 |
150 |

{% for p in exacts %} 151 | {% if p.user.avatar %} 152 | 153 | {% else %} 154 | 155 | {% endif %} 156 | {% endfor %}

157 | {% with winners_count=winners.count %} 158 |

Hubo {{ winners_count }} persona{{ winners_count|pluralize }} 159 | que sum{{ winners_count|pluralize:"ó,aron"}} {% if match.starred %}2 puntos (estrella){% else %}1 punto{% endif %}.

160 | {% endwith %} 161 |
162 |
163 |
164 |
165 | {% endif %} 166 | 167 | 168 |
169 | 180 | 181 | 182 | {% endblock %} 183 | -------------------------------------------------------------------------------- /ega/migrations/0001_squashed_0009_match_finished.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.4 on 2018-02-18 16:12 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | import django.contrib.auth.models 7 | import django.contrib.auth.validators 8 | from django.db import migrations, models 9 | import django.db.models.deletion 10 | import django.utils.timezone 11 | import ega.models 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | replaces = [('ega', '0001_initial'), ('ega', '0002_auto_20150419_1646'), ('ega', '0003_auto_20150426_1616'), ('ega', '0004_tournament_finished'), ('ega', '0005_auto_20150609_1938'), ('ega', '0006_auto_20151230_1602'), ('ega', '0007_auto_20160518_2020'), ('ega', '0008_auto_20161226_1656'), ('ega', '0009_match_finished')] 17 | 18 | initial = True 19 | 20 | dependencies = [ 21 | ('auth', '0008_alter_user_username_max_length'), 22 | ] 23 | 24 | operations = [ 25 | migrations.CreateModel( 26 | name='EgaUser', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('password', models.CharField(max_length=128, verbose_name='password')), 30 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 31 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 32 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 33 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 34 | ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), 35 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 36 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 37 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 38 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 39 | ('avatar', models.ImageField(blank=True, help_text='Se recomienda subir una imagen de (al menos) 100x100', null=True, upload_to='avatars')), 40 | ('invite_key', models.CharField(default=ega.models.rand_str, max_length=20, unique=True)), 41 | ('referred_on', models.DateTimeField(null=True)), 42 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 43 | ('referred_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='referrals', to=settings.AUTH_USER_MODEL)), 44 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 45 | ], 46 | options={ 47 | 'verbose_name_plural': 'users', 48 | 'verbose_name': 'user', 49 | 'abstract': False, 50 | }, 51 | managers=[ 52 | ('objects', django.contrib.auth.models.UserManager()), 53 | ], 54 | ), 55 | migrations.CreateModel( 56 | name='League', 57 | fields=[ 58 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 59 | ('name', models.CharField(max_length=200)), 60 | ('slug', models.SlugField(max_length=200)), 61 | ('created', models.DateTimeField(default=django.utils.timezone.now)), 62 | ], 63 | options={ 64 | 'ordering': ['name'], 65 | }, 66 | ), 67 | migrations.CreateModel( 68 | name='LeagueMember', 69 | fields=[ 70 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 71 | ('is_owner', models.BooleanField(default=False)), 72 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), 73 | ('league', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.League')), 74 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 75 | ], 76 | ), 77 | migrations.CreateModel( 78 | name='Match', 79 | fields=[ 80 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 81 | ('home_goals', models.IntegerField(blank=True, null=True)), 82 | ('away_goals', models.IntegerField(blank=True, null=True)), 83 | ('round', models.CharField(blank=True, max_length=128)), 84 | ('knockout', models.BooleanField(default=False)), 85 | ('description', models.CharField(blank=True, max_length=128)), 86 | ('when', models.DateTimeField(blank=True, null=True)), 87 | ('location', models.CharField(blank=True, max_length=200)), 88 | ('referee', models.CharField(blank=True, max_length=200)), 89 | ('starred', models.BooleanField(default=False)), 90 | ('suspended', models.BooleanField(default=False)), 91 | ('finished', models.BooleanField(default=False)), 92 | ], 93 | options={ 94 | 'ordering': ('when',), 95 | }, 96 | ), 97 | migrations.CreateModel( 98 | name='Prediction', 99 | fields=[ 100 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 101 | ('home_goals', models.PositiveIntegerField(blank=True, null=True)), 102 | ('away_goals', models.PositiveIntegerField(blank=True, null=True)), 103 | ('trend', models.CharField(editable=False, max_length=1)), 104 | ('starred', models.BooleanField(default=False)), 105 | ('score', models.PositiveIntegerField(default=0)), 106 | ('match', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.Match')), 107 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 108 | ], 109 | options={ 110 | 'ordering': ('match__when',), 111 | }, 112 | ), 113 | migrations.CreateModel( 114 | name='Team', 115 | fields=[ 116 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 117 | ('name', models.CharField(max_length=200)), 118 | ('code', models.CharField(blank=True, max_length=8)), 119 | ('slug', models.SlugField(max_length=200, unique=True)), 120 | ('image', models.ImageField(blank=True, null=True, upload_to='teams')), 121 | ], 122 | ), 123 | migrations.CreateModel( 124 | name='TeamStats', 125 | fields=[ 126 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 127 | ('zone', models.CharField(blank=True, default='', max_length=64)), 128 | ('won', models.PositiveIntegerField(default=0)), 129 | ('tie', models.PositiveIntegerField(default=0)), 130 | ('lost', models.PositiveIntegerField(default=0)), 131 | ('gf', models.PositiveIntegerField(default=0)), 132 | ('gc', models.PositiveIntegerField(default=0)), 133 | ('points', models.PositiveIntegerField(default=0)), 134 | ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.Team')), 135 | ], 136 | options={ 137 | 'ordering': ('-points',), 138 | }, 139 | ), 140 | migrations.CreateModel( 141 | name='Tournament', 142 | fields=[ 143 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 144 | ('name', models.CharField(max_length=200)), 145 | ('slug', models.SlugField(max_length=200, unique=True)), 146 | ('published', models.BooleanField(default=False)), 147 | ('finished', models.BooleanField(default=False)), 148 | ('teams', models.ManyToManyField(to='ega.Team')), 149 | ], 150 | ), 151 | migrations.AddField( 152 | model_name='teamstats', 153 | name='tournament', 154 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.Tournament'), 155 | ), 156 | migrations.AddField( 157 | model_name='match', 158 | name='away', 159 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='away_games', to='ega.Team'), 160 | ), 161 | migrations.AddField( 162 | model_name='match', 163 | name='home', 164 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='home_games', to='ega.Team'), 165 | ), 166 | migrations.AddField( 167 | model_name='match', 168 | name='tournament', 169 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.Tournament'), 170 | ), 171 | migrations.AddField( 172 | model_name='league', 173 | name='members', 174 | field=models.ManyToManyField(through='ega.LeagueMember', to=settings.AUTH_USER_MODEL), 175 | ), 176 | migrations.AddField( 177 | model_name='league', 178 | name='tournament', 179 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ega.Tournament'), 180 | ), 181 | migrations.AlterUniqueTogether( 182 | name='teamstats', 183 | unique_together=set([('tournament', 'team')]), 184 | ), 185 | migrations.AlterUniqueTogether( 186 | name='prediction', 187 | unique_together=set([('user', 'match')]), 188 | ), 189 | migrations.AlterUniqueTogether( 190 | name='leaguemember', 191 | unique_together=set([('user', 'league')]), 192 | ), 193 | migrations.AlterUniqueTogether( 194 | name='league', 195 | unique_together=set([('name', 'tournament'), ('slug', 'tournament')]), 196 | ), 197 | ] 198 | -------------------------------------------------------------------------------- /fixtures/sample_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "flatpages.flatpage", 4 | "fields": { 5 | "sites": [ 6 | 1 7 | ], 8 | "template_name": "", 9 | "url": "/faq/", 10 | "registration_required": false, 11 | "title": "Preguntas frecuentes", 12 | "content": "
\r\n\r\n
\u00bfQu\u00e9 es el-ega?
\r\n
Un juego simple de pron\u00f3sticos deportivos: intent\u00e1 acertar los resultados de los partidos y gan\u00e1 puntos de acuerdo a la precisi\u00f3n de tus pron\u00f3sticos.
\r\n\r\n
\u00bfQu\u00e9 hace falta para participar?
\r\n
Tener una cuenta de correo electr\u00f3nico y aceptar las reglas de juego.
\r\n\r\n
\u00bfC\u00f3mo sumo puntos?
\r\n
Sum\u00e1s puntos por acertar el resultado de un partido, ya sea de manera exacta o aproximada (es decir, si acert\u00e1s qui\u00e9n gan\u00f3, o si empataron, aunque no con el resultado correcto).
\r\n\r\n
\u00bfQu\u00e9 es un \"partido estrella\"?
\r\n
Partidos que entregan m\u00e1s puntos que los tradicionales (1 punto m\u00e1s si acert\u00e1s de manera exacta o aproximada)
\r\n\r\n
\u00bfD\u00f3nde puedo obtener m\u00e1s informaci\u00f3n?
\r\n
En la secci\u00f3n de las reglas de juego y a trav\u00e9s del foro de la p\u00e1gina, en nuestro formulario de contacto o en la direcci\u00f3n de mail [info (arroba) el-ega (punto) com (punto) ar].\r\nEn estos \u00faltimos se aceptan (y son bienvenidos) comentarios, ideas y sugerencias.
\r\n\r\n
", 13 | "enable_comments": false 14 | } 15 | }, 16 | { 17 | "model": "flatpages.flatpage", 18 | "fields": { 19 | "sites": [ 20 | 1 21 | ], 22 | "template_name": "", 23 | "url": "/rules/", 24 | "registration_required": false, 25 | "title": "Reglas del juego", 26 | "content": "

Participantes

\r\n

Podr\u00e1s participar si est\u00e1s previamente registrado. Para ello, complet\u00e1 el formulario de inscripci\u00f3n, o conectate a trav\u00e9s de tu cuenta de Facebook, Twitter o Google.

\r\n

Utilizaremos tu direcci\u00f3n de correo electr\u00f3nico para las comunicaciones de actualizaciones, novedades, informes, cambios de reglamento, etc.

\r\n\r\n

Modalidad de juego

\r\n

Cada usuario tiene una fecha l\u00ed\u00admite por partido para completar los resultados, de cuyo marcador real saldr\u00e1 la puntuaci\u00f3n que logre. Una vez vencida la fecha l\u00ed\u00admite de un partido, este no estar\u00e1 disponible para el juego. Hasta entonces, cualquier resultado puede ser modificado arbitrariamente.\r\nSi no complet\u00e1s el resultado para uno o ambos equipos, se considerar\u00e1 como \"no completado\" y no sumar\u00e1s puntos para ese partido.

\r\n\r\n

Puntuaci\u00f3n

\r\n

Los resultados reales de los partidos determinan la cantidad de puntos que se pueden obtener. En caso de alargue se lo considerar\u00e1 como parte del partido; es decir, en estos casos, el partido se considera terminado cuando finalice el alargue, en caso de existir. Las definiciones por penales no ser\u00e1n consideradas como parte del partido, el cual ser\u00e1 considerado empatado.

\r\n

La puntuaci\u00f3n se determina de la siguiente manera:\r\n

    \r\n
  • 3 puntos por acertar el resultado exacto del partido (4 en caso de partido \"estrella\")
  • \r\n
  • 1 punto por acertar el resultado del partido, pero no de manera exacta (2 en caso de partido \"estrella\")
  • \r\n
  • 0 punto si no se cumple ninguna de las condiciones anteriores\r\n
\r\n

\r\n\r\n

Posiciones

\r\n

Los usuarios ser\u00e1n ordenados en la tabla de posiciones de acuerdo a la cantidad de puntos que hayan obtenido en el total de partidos disputados. En caso de igualdad, se tomar\u00e1n los siguientes criterios para determinar el desempate (en orden decreciente de prioridad):

\r\n

\r\n

    \r\n
  • Mayor cantidad de resultados exactos acertados
  • \r\n
  • Mayor cantidad de resultados \"no exactos\" acertados
  • \r\n
  • Menor cantidad de beneficios recibidos por partidos \"estrella\"
  • \r\n
  • Sorteo
  • \r\n
\r\n

\r\n\r\n

Partidos suspendidos o cancelados

\r\n

Si alg\u00fan partido resultara suspendido o postergado, podr\u00e1 ser cancelado tambi\u00e9n del juego, si lo creemos conveniente.\r\nUn partido tambi\u00e9n puede resultar cancelado si:\r\n

    \r\n
  • La fecha de cierre fue modificada sin tener los usuarios un tiempo razonable para jugar
  • \r\n
  • Se pude completar el resultado de un partido cuando \u00e9ste ya est\u00e1 en juego
  • \r\n
\r\n

", 27 | "enable_comments": false 28 | } 29 | }, 30 | { 31 | "model": "ega.team", 32 | "fields": { 33 | "image": "teams/Arsenal.png", 34 | "slug": "arsenal", 35 | "name": "Arsenal", 36 | "code": "ARS" 37 | } 38 | }, 39 | { 40 | "model": "ega.team", 41 | "fields": { 42 | "image": "teams/Banfield.png", 43 | "slug": "banfield", 44 | "name": "Banfield", 45 | "code": "BAN" 46 | } 47 | }, 48 | { 49 | "model": "ega.team", 50 | "fields": { 51 | "image": "teams/Boca.png", 52 | "slug": "boca-juniors", 53 | "name": "Boca Juniors", 54 | "code": "BOC" 55 | } 56 | }, 57 | { 58 | "model": "ega.team", 59 | "fields": { 60 | "image": "teams/Estudiantes.png", 61 | "slug": "estudiantes", 62 | "name": "Estudiantes", 63 | "code": "EST" 64 | } 65 | }, 66 | { 67 | "model": "ega.team", 68 | "fields": { 69 | "image": "teams/Gimnasia.png", 70 | "slug": "gimnasia", 71 | "name": "Gimnasia", 72 | "code": "GLP" 73 | } 74 | }, 75 | { 76 | "model": "ega.team", 77 | "fields": { 78 | "image": "teams/Lanus.png", 79 | "slug": "lanus", 80 | "name": "Lan\u00fas", 81 | "code": "LAN" 82 | } 83 | }, 84 | { 85 | "model": "ega.team", 86 | "fields": { 87 | "image": "teams/Newells.png", 88 | "slug": "newells-old-boys", 89 | "name": "Newell's Old Boys", 90 | "code": "NOB" 91 | } 92 | }, 93 | { 94 | "model": "ega.team", 95 | "fields": { 96 | "image": "teams/Olimpo.png", 97 | "slug": "olimpo", 98 | "name": "Olimpo", 99 | "code": "OLI" 100 | } 101 | }, 102 | { 103 | "model": "ega.team", 104 | "fields": { 105 | "image": "teams/Quilmes.png", 106 | "slug": "quilmes", 107 | "name": "Quilmes", 108 | "code": "QUI" 109 | } 110 | }, 111 | { 112 | "model": "ega.team", 113 | "fields": { 114 | "image": "teams/Racing.png", 115 | "slug": "racing", 116 | "name": "Racing", 117 | "code": "RAC" 118 | } 119 | }, 120 | { 121 | "model": "ega.team", 122 | "fields": { 123 | "image": "teams/River.png", 124 | "slug": "river-plate", 125 | "name": "River Plate", 126 | "code": "RIV" 127 | } 128 | }, 129 | { 130 | "model": "ega.team", 131 | "fields": { 132 | "image": "teams/RosarioCentral.png", 133 | "slug": "rosario-central", 134 | "name": "Rosario Central", 135 | "code": "CEN" 136 | } 137 | }, 138 | { 139 | "model": "ega.team", 140 | "fields": { 141 | "image": "teams/SanLorenzo.png", 142 | "slug": "san-lorenzo", 143 | "name": "San Lorenzo", 144 | "code": "SLO" 145 | } 146 | }, 147 | { 148 | "model": "ega.team", 149 | "fields": { 150 | "image": "teams/Velez.png", 151 | "slug": "velez", 152 | "name": "V\u00e9lez", 153 | "code": "VEL" 154 | } 155 | }, 156 | { 157 | "model": "ega.team", 158 | "fields": { 159 | "image": "teams/Rafaela.png", 160 | "slug": "atletico-rafaela", 161 | "name": "Atl\u00e9tico Rafaela", 162 | "code": "RAF" 163 | } 164 | }, 165 | { 166 | "model": "ega.team", 167 | "fields": { 168 | "image": "teams/Belgrano.png", 169 | "slug": "belgrano", 170 | "name": "Belgrano", 171 | "code": "BEL" 172 | } 173 | }, 174 | { 175 | "model": "ega.team", 176 | "fields": { 177 | "image": "teams/Defensa.png", 178 | "slug": "defensa-y-justicia", 179 | "name": "Defensa y Justicia", 180 | "code": "DYJ" 181 | } 182 | }, 183 | { 184 | "model": "ega.team", 185 | "fields": { 186 | "image": "teams/GodoyCruz.png", 187 | "slug": "godoy-cruz", 188 | "name": "Godoy Cruz", 189 | "code": "GOD" 190 | } 191 | }, 192 | { 193 | "model": "ega.team", 194 | "fields": { 195 | "image": "teams/Tigre.png", 196 | "slug": "tigre", 197 | "name": "Tigre", 198 | "code": "TIG" 199 | } 200 | }, 201 | { 202 | "model": "ega.team", 203 | "fields": { 204 | "image": "teams/Independiente.png", 205 | "slug": "independiente", 206 | "name": "Independiente", 207 | "code": "IND" 208 | } 209 | }, 210 | { 211 | "model": "ega.team", 212 | "fields": { 213 | "image": "teams/Aldosivi.png", 214 | "slug": "aldosivi", 215 | "name": "Aldosivi", 216 | "code": "ALD" 217 | } 218 | }, 219 | { 220 | "model": "ega.team", 221 | "fields": { 222 | "image": "teams/Colon.png", 223 | "slug": "colon", 224 | "name": "Col\u00f3n", 225 | "code": "COL" 226 | } 227 | }, 228 | { 229 | "model": "ega.team", 230 | "fields": { 231 | "image": "teams/Crucero.png", 232 | "slug": "crucero-del-norte", 233 | "name": "Crucero del Norte", 234 | "code": "CDN" 235 | } 236 | }, 237 | { 238 | "model": "ega.team", 239 | "fields": { 240 | "image": "teams/SanMartin.png", 241 | "slug": "san-martin-sj", 242 | "name": "San Mart\u00edn (SJ)", 243 | "code": "SMA" 244 | } 245 | }, 246 | { 247 | "model": "ega.team", 248 | "fields": { 249 | "image": "teams/Temperley.png", 250 | "slug": "temperley", 251 | "name": "Temperley", 252 | "code": "TEM" 253 | } 254 | }, 255 | { 256 | "model": "ega.team", 257 | "fields": { 258 | "image": "teams/Union.png", 259 | "slug": "union", 260 | "name": "Uni\u00f3n", 261 | "code": "UNI" 262 | } 263 | }, 264 | { 265 | "model": "ega.team", 266 | "fields": { 267 | "image": "teams/Huracan.png", 268 | "slug": "huracan", 269 | "name": "Hurac\u00e1n", 270 | "code": "HUR" 271 | } 272 | }, 273 | { 274 | "model": "ega.team", 275 | "fields": { 276 | "image": "teams/Argentinos.png", 277 | "slug": "argentinos-juniors", 278 | "name": "Argentinos Juniors", 279 | "code": "ARG" 280 | } 281 | }, 282 | { 283 | "model": "ega.team", 284 | "fields": { 285 | "image": "teams/Sarmiento.png", 286 | "slug": "sarmiento", 287 | "name": "Sarmiento", 288 | "code": "SAR" 289 | } 290 | }, 291 | { 292 | "model": "ega.team", 293 | "fields": { 294 | "image": "teams/Chicago.png", 295 | "slug": "nueva-chicago", 296 | "name": "Nueva Chicago", 297 | "code": "CHI" 298 | } 299 | }, 300 | { 301 | "model": "ega.team", 302 | "fields": { 303 | "image": "teams/Patronato.png", 304 | "slug": "patronato", 305 | "name": "Patronato", 306 | "code": "PAT" 307 | } 308 | }, 309 | { 310 | "model": "ega.team", 311 | "fields": { 312 | "image": "teams/AtleticoTucuman.png", 313 | "slug": "atletico-tucuman", 314 | "name": "Atlético Tucumán", 315 | "code": "TUC" 316 | } 317 | }, 318 | { 319 | "model": "ega.tournament", 320 | "fields": { 321 | "finished": false, 322 | "teams": [ 323 | 33, 324 | 34, 325 | 35, 326 | 36, 327 | 38, 328 | 40, 329 | 41, 330 | 42, 331 | 43, 332 | 45, 333 | 46, 334 | 47, 335 | 48, 336 | 49, 337 | 51, 338 | 52, 339 | 54, 340 | 57, 341 | 58, 342 | 59, 343 | 61, 344 | 55, 345 | 62, 346 | 44, 347 | 60, 348 | 37, 349 | 50, 350 | 56, 351 | 53, 352 | 39, 353 | 63, 354 | 64 355 | ], 356 | "slug": "torneo-transicion-2016", 357 | "name": "Torneo de Transición 2016", 358 | "published": true 359 | } 360 | } 361 | ] 362 | -------------------------------------------------------------------------------- /ega/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | 4 | from datetime import timedelta 5 | 6 | from allauth.account.models import EmailAddress 7 | from django.contrib import auth, messages 8 | from django.contrib.auth.decorators import login_required 9 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 10 | from django.db.models import Q 11 | from django.forms.models import modelformset_factory 12 | from django.http import Http404, HttpResponse, HttpResponseRedirect 13 | from django.shortcuts import get_object_or_404, render 14 | from django.urls import reverse 15 | from django.utils.timezone import now 16 | from django.views.decorators.http import require_GET, require_http_methods 17 | 18 | from ega.constants import ( 19 | EXACTLY_MATCH_POINTS, 20 | HISTORY_MATCHES_PER_PAGE, 21 | HOURS_TO_DEADLINE, 22 | INVITE_BODY, 23 | INVITE_LEAGUE, 24 | INVITE_SUBJECT, 25 | NEXT_MATCHES_DAYS, 26 | RANKING_TEAMS_PER_PAGE, 27 | ) 28 | from ega.forms import ( 29 | ChampionPredictionForm, 30 | EgaUserForm, 31 | InviteFriendsForm, 32 | LeagueForm, 33 | PredictionForm, 34 | ) 35 | from ega.models import ( 36 | ChampionPrediction, 37 | EgaUser, 38 | League, 39 | LeagueMember, 40 | Match, 41 | Prediction, 42 | Tournament, 43 | ) 44 | 45 | 46 | def build_invite_url(request, slug, key=None, league_slug=None): 47 | if key is None: 48 | key = request.user.invite_key 49 | 50 | kwargs = dict(key=key, slug=slug) 51 | if league_slug is not None: 52 | kwargs['league_slug'] = league_slug 53 | 54 | return request.build_absolute_uri(reverse('ega-join', kwargs=kwargs)) 55 | 56 | 57 | def logout(request): 58 | auth.logout(request) 59 | messages.success(request, 'Cerraste sesión exitosamente!') 60 | return HttpResponseRedirect(reverse('meta-home')) 61 | 62 | 63 | def _next_matches(user): 64 | tz_now = now() + timedelta(hours=HOURS_TO_DEADLINE) 65 | until = tz_now + timedelta(days=NEXT_MATCHES_DAYS) 66 | matches = Match.objects.select_related('tournament').filter( 67 | tournament__published=True, tournament__finished=False, 68 | when__range=(tz_now, until)).order_by('tournament', 'when') 69 | predictions = user.prediction_set.filter( 70 | match__in=matches, home_goals__isnull=False, away_goals__isnull=False) 71 | next_matches = [] 72 | for m in matches: 73 | pred = None 74 | for p in predictions: 75 | if p.match == m: 76 | pred = p 77 | break 78 | row = {'match': m, 'prediction': pred} 79 | next_matches.append(row) 80 | return next_matches 81 | 82 | 83 | @login_required 84 | def meta_home(request): 85 | try: 86 | t = Tournament.objects.get(published=True, finished=False) 87 | return HttpResponseRedirect(reverse('ega-home', args=[t.slug])) 88 | except (Tournament.DoesNotExist, Tournament.MultipleObjectsReturned): 89 | pass 90 | 91 | past_tournaments = Tournament.objects.filter( 92 | published=True, finished=True).order_by('-id') 93 | next_matches = _next_matches(request.user) 94 | return render(request, 'ega/meta_home.html', 95 | {'next_matches': next_matches, 96 | 'past_tournaments': past_tournaments}) 97 | 98 | 99 | @login_required 100 | def home(request, slug): 101 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 102 | matches = tournament.next_matches() 103 | played = Prediction.objects.filter( 104 | user=request.user, match__in=matches, 105 | home_goals__isnull=False, away_goals__isnull=False) 106 | 107 | current_round = tournament.current_round() 108 | matches = matches[:3] 109 | for m in matches: 110 | try: 111 | m.user_prediction = played.get(match=m) 112 | except Prediction.DoesNotExist: 113 | m.user_prediction = None 114 | 115 | top_ranking = tournament.ranking()[:7] 116 | history = request.user.history(tournament)[:3] 117 | stats = request.user.stats(tournament) 118 | 119 | champion_form = None 120 | if slug == 'rusia-2018': 121 | champion, _ = ChampionPrediction.objects.get_or_create( 122 | user=request.user, tournament=tournament) 123 | champion_form = ChampionPredictionForm(instance=champion) 124 | 125 | return render( 126 | request, 'ega/home.html', 127 | {'top_ranking': top_ranking, 128 | 'tournament': tournament, 'current_round': current_round, 129 | 'matches': matches, 'history': history, 130 | 'stats': stats, 'champion_form': champion_form}) 131 | 132 | 133 | @require_http_methods(('POST',)) 134 | @login_required 135 | def update_champion_prediction(request, slug): 136 | """Update user prediction for a tournament champion.""" 137 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 138 | prediction, _ = ChampionPrediction.objects.get_or_create( 139 | user=request.user, tournament=tournament) 140 | 141 | form = ChampionPredictionForm(instance=prediction, data=request.POST) 142 | if form.is_valid(): 143 | form.save() 144 | team = form.cleaned_data['team'] 145 | team_name = team.name if team is not None else '-' 146 | messages.success( 147 | request, 'Pronóstico de campeón actualizado: %s.' % team_name) 148 | return HttpResponseRedirect(reverse('ega-home', args=[slug])) 149 | 150 | messages.error(request, 'Equipo no válido') 151 | return HttpResponseRedirect(reverse('ega-home', args=[slug])) 152 | 153 | 154 | @require_http_methods(('GET', 'POST')) 155 | @login_required 156 | def profile(request): 157 | if request.method == 'POST': 158 | form = EgaUserForm( 159 | instance=request.user, data=request.POST, files=request.FILES) 160 | if form.is_valid(): 161 | form.save() 162 | messages.success(request, 'Perfil actualizado.') 163 | return HttpResponseRedirect(reverse('profile')) 164 | else: 165 | form = EgaUserForm(instance=request.user) 166 | return render(request, 'ega/profile.html', dict(form=form)) 167 | 168 | 169 | @require_http_methods(('GET', 'POST')) 170 | @login_required 171 | def invite_friends(request, slug, league_slug=None): 172 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 173 | 174 | league = None 175 | if league_slug: 176 | league = get_object_or_404( 177 | League, tournament=tournament, slug=league_slug) 178 | if league.owner != request.user: 179 | raise Http404 180 | invite_url = build_invite_url( 181 | request, slug=tournament.slug, league_slug=league_slug) 182 | 183 | if request.method == 'POST': 184 | form = InviteFriendsForm(request.POST) 185 | if form.is_valid(): 186 | emails = form.invite(sender=request.user) 187 | if emails > 1: 188 | msg = '%s amigos invitados!' % emails 189 | else: 190 | msg = '1 amigo invitado!' 191 | messages.success(request, msg) 192 | return HttpResponseRedirect(reverse('ega-home', args=[slug])) 193 | else: 194 | subject = INVITE_SUBJECT 195 | extra_text = '' 196 | if league: 197 | subject += ', jugando en mi liga de amigos %s' % league.name 198 | extra_text = INVITE_LEAGUE % dict(league_name=league.name) 199 | 200 | initial = dict( 201 | subject=subject, 202 | body=INVITE_BODY % dict( 203 | extra_text=extra_text, url=invite_url, 204 | inviter=request.user.visible_name()), 205 | ) 206 | form = InviteFriendsForm(initial=initial) 207 | 208 | return render(request, 'ega/invite.html', 209 | dict(tournament=tournament, form=form, 210 | league=league, invite_url=invite_url)) 211 | 212 | 213 | @require_GET 214 | @login_required 215 | def friend_join(request, key, slug, league_slug=None): 216 | inviting_user = get_object_or_404(EgaUser, invite_key=key) 217 | if inviting_user == request.user: 218 | invite_url = build_invite_url( 219 | request, slug=slug, league_slug=league_slug) 220 | msg1 = 'Vos sos el dueño del link %s!' % invite_url 221 | msg2 = 'No podés unirte con un link de referencia propio.' 222 | messages.info(request, msg1) 223 | messages.warning(request, msg2) 224 | return HttpResponseRedirect(reverse('ega-home', args=[slug])) 225 | 226 | created = inviting_user.record_referral(request.user) 227 | if created: 228 | msg = 'Te uniste a el Ega! ' 229 | else: 230 | msg = 'Hola de nuevo! ' 231 | 232 | if league_slug: 233 | league = get_object_or_404( 234 | League, tournament__slug=slug, slug=league_slug) 235 | member, created = LeagueMember.objects.get_or_create( 236 | user=request.user, league=league) 237 | if created: 238 | msg += 'Bienvenido a la liga %s.' % league 239 | else: 240 | msg += 'Ya sos miembro de la liga %s.' % league 241 | 242 | messages.success(request, msg) 243 | # switch to the tournament this user was invited to 244 | return HttpResponseRedirect(reverse('ega-home', kwargs=dict(slug=slug))) 245 | 246 | 247 | @require_http_methods(('GET', 'POST')) 248 | @login_required 249 | def leagues(request, slug): 250 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 251 | 252 | if request.method == 'POST': 253 | form = LeagueForm(request.POST) 254 | if form.is_valid(): 255 | league = form.save(commit=False) 256 | league.tournament = tournament 257 | league.save() 258 | LeagueMember.objects.create( 259 | user=request.user, league=league, is_owner=True) 260 | return HttpResponseRedirect( 261 | reverse('ega-invite-league', 262 | kwargs=dict(slug=slug, league_slug=league.slug))) 263 | else: 264 | form = LeagueForm() 265 | 266 | user_leagues = League.objects.filter( 267 | tournament=tournament, members=request.user) 268 | return render( 269 | request, 'ega/leagues.html', 270 | dict(tournament=tournament, leagues=user_leagues, form=form)) 271 | 272 | 273 | @require_GET 274 | @login_required 275 | def league_home(request, slug, league_slug): 276 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 277 | league = get_object_or_404( 278 | League, slug=league_slug, members=request.user, 279 | tournament=tournament, tournament__published=True) 280 | 281 | top_ranking = league.ranking()[:5] 282 | stats = request.user.stats(tournament) 283 | 284 | return render( 285 | request, 'ega/league_home.html', 286 | {'tournament': tournament, 'league': league, 287 | 'top_ranking': top_ranking, 'stats': stats}) 288 | 289 | 290 | @login_required 291 | def next_matches(request, slug): 292 | """Return coming matches for the specified tournament.""" 293 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 294 | # create empty predictions if needed 295 | missing = Match.objects.filter( 296 | tournament=tournament).exclude(prediction__user=request.user) 297 | Prediction.objects.bulk_create([ 298 | Prediction(user=request.user, match=m) for m in missing 299 | ]) 300 | 301 | # predictions for the next matches 302 | tz_now = now() + timedelta(hours=HOURS_TO_DEADLINE) 303 | until = tz_now + timedelta(days=NEXT_MATCHES_DAYS) 304 | predictions = Prediction.objects.filter( 305 | user=request.user, match__tournament=tournament, 306 | match__when__range=(tz_now, until)) 307 | 308 | PredictionFormSet = modelformset_factory( 309 | Prediction, form=PredictionForm, extra=0) 310 | if request.method == 'POST': 311 | formset = PredictionFormSet(request.POST) 312 | if formset.is_valid(): 313 | formset.save() 314 | 315 | if request.is_ajax(): 316 | return HttpResponse(json.dumps({'ok': True})) 317 | 318 | messages.success(request, 'Pronósticos actualizados.') 319 | expired_matches = [f.instance.match for f in formset if f.expired] 320 | for m in expired_matches: 321 | msg = "%s - %s: el partido expiró, pronóstico NO actualizado." 322 | messages.error(request, msg % (m.home.name, m.away.name)) 323 | 324 | return HttpResponseRedirect(reverse('ega-home', args=[slug])) 325 | 326 | # invalid form 327 | if request.is_ajax(): 328 | return HttpResponse(json.dumps({'ok': False})) 329 | 330 | else: 331 | formset = PredictionFormSet(queryset=predictions) 332 | 333 | return render(request, 'ega/next_matches.html', 334 | {'tournament': tournament, 'formset': formset}) 335 | 336 | 337 | @login_required 338 | def match_details(request, slug, match_id): 339 | """Return specified match stats.""" 340 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 341 | match = get_object_or_404( 342 | Match, id=match_id, tournament=tournament, tournament__published=True) 343 | 344 | exacts = Prediction.objects.none() 345 | winners = Prediction.objects.none() 346 | if match.finished: 347 | winners = Prediction.objects.filter( 348 | match=match, score__gt=0, score__lt=EXACTLY_MATCH_POINTS) 349 | exacts = Prediction.objects.filter( 350 | match=match, score__gte=EXACTLY_MATCH_POINTS 351 | ).select_related('user') 352 | 353 | return render( 354 | request, 'ega/match_details.html', 355 | {'tournament': tournament, 'match': match, 356 | 'exacts': exacts, 'winners': winners}) 357 | 358 | 359 | @login_required 360 | def ranking(request, slug, league_slug=None, round=None): 361 | """Return ranking and stats for the specified tournament.""" 362 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 363 | league = None 364 | 365 | base_url = reverse('ega-ranking', args=[slug]) 366 | if league_slug is not None: 367 | base_url = reverse('ega-league-ranking', 368 | args=[tournament.slug, league_slug]) 369 | league = get_object_or_404( 370 | League, tournament=tournament, tournament__published=True, 371 | slug=league_slug) 372 | 373 | user = request.user 374 | scores = (league.ranking(round=round) 375 | if league else tournament.ranking(round=round)) 376 | try: 377 | position = ([r['username'] for r in scores]).index(user.username) 378 | position += 1 379 | except ValueError: 380 | position = None 381 | paginator = Paginator(scores, RANKING_TEAMS_PER_PAGE) 382 | 383 | page = request.GET.get('page') 384 | try: 385 | ranking = paginator.page(page) 386 | except PageNotAnInteger: 387 | ranking = paginator.page(1) 388 | except EmptyPage: 389 | ranking = paginator.page(paginator.num_pages) 390 | 391 | stats = user.stats(tournament, round=round) 392 | round_choices = tournament.match_set.filter( 393 | home_goals__isnull=False, away_goals__isnull=False).values_list( 394 | 'round', flat=True).order_by('round').distinct() 395 | user_leagues = League.objects.filter( 396 | tournament=tournament, members=request.user) 397 | 398 | return render( 399 | request, 'ega/ranking.html', 400 | {'tournament': tournament, 'league': league, 'leagues': user_leagues, 401 | 'base_url': base_url, 'round': round, 'choices': round_choices, 402 | 'ranking': ranking, 'user_position': position, 'stats': stats}) 403 | 404 | 405 | @login_required 406 | def history(request, slug): 407 | """Return history for the specified tournament.""" 408 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 409 | user_history = request.user.history(tournament) 410 | paginator = Paginator(user_history, HISTORY_MATCHES_PER_PAGE) 411 | 412 | page = request.GET.get('page') 413 | try: 414 | predictions = paginator.page(page) 415 | except PageNotAnInteger: 416 | predictions = paginator.page(1) 417 | except EmptyPage: 418 | predictions = paginator.page(paginator.num_pages) 419 | 420 | stats = request.user.stats(tournament) 421 | 422 | return render( 423 | request, 'ega/history.html', 424 | {'tournament': tournament, 'predictions': predictions, 'stats': stats}) 425 | 426 | 427 | def stats(request, slug): 428 | """Return stats for the specified tournament.""" 429 | tournament = get_object_or_404(Tournament, slug=slug, published=True) 430 | 431 | results = tournament.most_common_results(5) 432 | predictions = tournament.most_common_predictions(5) 433 | ranking = tournament.team_ranking() 434 | 435 | no_wins = [r.team for r in ranking if r.won == 0] 436 | no_ties = [r.team for r in ranking if r.tie == 0] 437 | no_loses = [r.team for r in ranking if r.lost == 0] 438 | 439 | return render( 440 | request, 'ega/stats.html', 441 | {'tournament': tournament, 'ranking': ranking, 442 | 'top_5': zip(results, predictions), 443 | 'no_wins': no_wins, 'no_ties': no_ties, 'no_loses': no_loses}) 444 | 445 | 446 | @login_required 447 | def verify_email(request, email): 448 | email_address = get_object_or_404( 449 | EmailAddress, user=request.user, email=email) 450 | email_address.send_confirmation(request) 451 | messages.success(request, 'Email de verificación enviado a %s' % email) 452 | return HttpResponseRedirect(reverse('profile')) 453 | --------------------------------------------------------------------------------