├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
18 | {% for m in league.leaguemember_set.all %}
19 |
20 | {{ m.user.visible_name }}
21 | {% ifequal m.user user %}vos {% endifequal %}
22 | {% if m.is_owner %}creador {% endif %}
23 |
24 | {% endfor %}
25 |
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 |
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 |
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 |
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 |
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 | [](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 |
28 |
29 |
30 |
31 |
32 | {% include 'ega/_user_stats.html' %}
33 |
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 |
38 |
39 |
40 |
41 |
42 | {% endblock %}
43 |
--------------------------------------------------------------------------------
/ega/templates/ega/_ranking_table.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 | #
7 |
8 | Usuario
9 | {% if score_details %}
10 | x1
11 | x3
12 |
13 | {% endif %}
14 | Puntos
15 |
16 |
17 |
18 | {% for row in ranking %}
19 |
20 | {{ forloop.counter0|add:delta }}
21 | {% if row.avatar %}
22 |
23 | {% else %}
24 |
25 | {% endif %}
26 |
27 | {{ row.username }}
28 | {% if score_details %}
29 | {{ row.x1|add:row.xx1 }}
30 | {{ row.x3|add:row.xx3 }}
31 | {% if row.xx1 or row.xx3 %}+{{ row.xx1|add:row.xx3 }}{% endif %}
32 | {% endif %}
33 | {{ row.total }}
34 |
35 | {% endfor %}
36 |
37 |
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 |
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 |
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 |
49 | {% include "socialaccount/snippets/provider_list.html" with process="connect" %}
50 |
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 |
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 | Últimos resultados
22 |
23 | {% for m in latest_matches|slice:':3' %}
24 |
25 | {{ m.home.name }}
26 | {{ m.home_goals|default_if_none:'-' }}
27 | {{ m.away_goals|default_if_none:'-' }}
28 | {{ m.away.name }}
29 |
30 | {% endfor %}
31 |
32 |
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 |
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 |
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 |
14 | {% if use_code %}{% firstof home.code home.name %}{% else %}{{ home }}{% endif %}
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 |
31 | {% if use_code %}{% firstof away.code away.name %}{% else %}{{ away }}{% endif %}
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 |
26 | Total
27 | {% for f in choices %}
28 |
30 | Fecha {{ f }}
31 | {% endfor %}
32 |
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 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | {% include 'ega/_user_stats.html' %}
75 |
76 | {% comment %}
77 |
Otras estadísticas
78 |
Mayor cantidad de exactos
79 |
Mayor cantidad de aciertos
80 |
Mayor cantidad de fechas ganadas
81 |
Equipo más votado como ganador
82 |
Equipo más votado como perdedor
83 | {% endcomment %}
84 |
85 |
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 |
30 |
31 |
32 | {% champion_predictions_chart tournament as chart %}
33 |
34 |
35 |
36 | {% elif tournament.slug == 'uruguay-clausura-2017' %}
37 |
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 |
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 | J
32 | G
33 | E
34 | P
35 | GF
36 | GC
37 | DG
38 | Puntos
39 |
40 |
41 |
42 | {% for row in zone.list %}
43 |
44 | {{ forloop.counter }}
45 | {% if row.team.image %}
46 |
47 | {% endif %}
48 |
49 | {{ row.team }}
50 | {{ row.played }}
51 | {{ row.won }}
52 | {{ row.tie }}
53 | {{ row.lost }}
54 | {{ row.gf }}
55 | {{ row.gc }}
56 |
57 | {% if row.dg >= 0 %}+{% endif %}{{ row.dg }}
58 | {{ row.points }}
59 |
60 | {% endfor %}
61 |
62 |
63 | {% endfor %}
64 |
65 |
66 |
67 |
68 |
Top 5 - Más repetidos
69 |
70 |
71 |
72 | Resultados
73 | Pronósticos
74 |
75 |
76 |
77 | {% for result, prediction in top_5 %}
78 |
79 | {{ result.0.0 }} - {{ result.0.1 }}
80 | ({{ result.1 }})
81 |
82 | {{ prediction.0.0 }} - {{ prediction.0.1 }}
83 | ({{ prediction.1 }})
84 |
85 | {% endfor %}
86 |
87 |
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 |
13 |
14 | {% latest_news source='ega' latest=3 %}
15 |
16 |
17 |
18 |
19 |
Próximos partidos
20 |
21 | {% regroup next_matches by match.tournament as match_list %}
22 |
23 |
24 | {% for tournament in match_list %}
25 |
26 | {{ tournament.grouper.name }}
27 | {% endfor %}
28 |
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 |
19 |
20 |
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 |
46 |
Logueate con
47 |
48 | {% for provider in socialaccount_providers %}
49 | {% if provider.id == 'twitter' %}
50 |
51 |
52 | {% endif %}
53 | {% if provider.id == 'google' %}
54 |
55 |
56 | {% endif %}
57 | {% if provider.id == 'facebook' %}
58 |
59 |
60 | {% endif %}
61 | {% endfor %}
62 |
63 | {% include "socialaccount/snippets/login_extra.html" %}
64 |
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 |
33 |
34 |
46 |
47 |
48 |
97 |
98 |
99 |
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 |
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 |
116 | {% if match.pk_away_goals != None %}[{{ match.pk_away_goals }}]{% endif %}
117 | {{ match.away_goals|default_if_none:"-" }}
118 |
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\nUn 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\nTener una cuenta de correo electr\u00f3nico y aceptar las reglas de juego. \r\n\r\n\u00bfC\u00f3mo sumo puntos? \r\nSum\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\nPartidos 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\nEn 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\nPodr\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\nUtilizaremos tu direcci\u00f3n de correo electr\u00f3nico para las comunicaciones de actualizaciones, novedades, informes, cambios de reglamento, etc.
\r\n\r\nModalidad de juego \r\nCada 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\nPuntuaci\u00f3n \r\nLos 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\nLa puntuaci\u00f3n se determina de la siguiente manera:\r\n
\r\n3 puntos por acertar el resultado exacto del partido (4 en caso de partido \"estrella\") \r\n1 punto por acertar el resultado del partido, pero no de manera exacta (2 en caso de partido \"estrella\") \r\n0 punto si no se cumple ninguna de las condiciones anteriores\r\n \r\n\r\n\r\nPosiciones \r\nLos 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\nMayor cantidad de resultados exactos acertados \r\nMayor cantidad de resultados \"no exactos\" acertados \r\nMenor cantidad de beneficios recibidos por partidos \"estrella\" \r\nSorteo \r\n \r\n\r\n\r\nPartidos suspendidos o cancelados \r\nSi 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\nLa fecha de cierre fue modificada sin tener los usuarios un tiempo razonable para jugar \r\nSe 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 |
--------------------------------------------------------------------------------