").css({
122 | "display": "none",
123 | "text-align": "center",
124 | "padding": "10px",
125 | "clear": "both"
126 | }).append($("
![]()
", {
127 | "src": this.loader_image
128 | }));
129 |
130 | $(this.content_selector).after(this.loader);
131 |
132 | },
133 |
134 | show_loader : function () {
135 |
136 | this.loader.show();
137 | },
138 |
139 | hide_loader : function () {
140 |
141 | this.loader.hide();
142 |
143 | },
144 |
145 | get_footer_threshold : function () {
146 |
147 | return this.is_mobile_device() ?
148 | this.MOBILE_FOOTER_POSITION_THRESHOLD : this.FOOTER_POSITION_THRESHOLD;
149 |
150 | },
151 |
152 | get_doc_height : function () {
153 |
154 | var D = document;
155 |
156 | return Math.max(
157 | Math.max(D.body.scrollHeight, D.documentElement.scrollHeight),
158 | Math.max(D.body.offsetHeight, D.documentElement.offsetHeight),
159 | Math.max(D.body.clientHeight, D.documentElement.clientHeight)
160 | );
161 |
162 | },
163 |
164 | is_mobile_device : function () {
165 |
166 | return navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad|android)/);
167 |
168 | }
169 |
170 | });
--------------------------------------------------------------------------------
/web/profiles/views.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django.contrib.auth import logout, login, authenticate
4 | from django.contrib.auth.forms import AuthenticationForm
5 | from django.contrib.auth.models import User
6 | from django.core.urlresolvers import reverse
7 | from django.http import HttpResponse
8 | from django.views.generic import FormView, CreateView, RedirectView, DetailView, UpdateView
9 |
10 | from profiles.forms import RegistrationForm, ProfileUpdateForm
11 | from profiles.models import Profile
12 | from profiles.signals import follow_done, unfollow_done
13 | from premises.models import Contention, Report
14 |
15 |
16 | class RegistrationView(CreateView):
17 | form_class = RegistrationForm
18 | template_name = "auth/register.html"
19 |
20 | def form_valid(self, form):
21 | response = super(RegistrationView, self).form_valid(form)
22 | user = authenticate(username=form.cleaned_data["username"],
23 | password=form.cleaned_data["password1"])
24 | login(self.request, user)
25 | return response
26 |
27 | def get_success_url(self):
28 | return reverse("home")
29 |
30 |
31 | class LoginView(FormView):
32 | form_class = AuthenticationForm
33 | template_name = "auth/login.html"
34 |
35 | def form_valid(self, form):
36 | login(self.request, form.get_user())
37 | return super(LoginView, self).form_valid(form)
38 |
39 | def get_success_url(self):
40 | return self.request.GET.get("next") or reverse("home")
41 |
42 | def get_context_data(self, **kwargs):
43 | context = super(LoginView, self).get_context_data(**kwargs)
44 | context["next"] = self.request.GET.get("next", "")
45 | return context
46 |
47 |
48 | class LogoutView(RedirectView):
49 | def get(self, request, *args, **kwargs):
50 | logout(request)
51 | return super(LogoutView, self).get(request, *args, **kwargs)
52 |
53 | def get_redirect_url(self, **kwargs):
54 | return reverse("home")
55 |
56 |
57 | class ProfileDetailView(DetailView):
58 | slug_field = 'username'
59 | slug_url_kwarg = 'username'
60 | context_object_name = "profile"
61 | model = Profile
62 |
63 | def get_context_data(self, **kwargs):
64 | """
65 | Adds extra context to template
66 | """
67 | user = self.get_object()
68 | contentions = Contention.objects.filter(
69 | premises__user=user
70 | ).distinct()
71 |
72 | if self.request.user != user:
73 | contentions = contentions.filter(is_published=True)
74 |
75 | can_follow = self.request.user != user
76 |
77 | if self.request.user.is_authenticated():
78 | is_followed = self.request.user.following.filter(pk=user.id).exists()
79 | else:
80 | is_followed = False
81 | return super(ProfileDetailView, self).get_context_data(
82 | can_follow=can_follow,
83 | is_followed=is_followed,
84 | contentions=contentions)
85 |
86 | def delete(self, request, **kwargs):
87 | """
88 | - Removes `FollowedProfile` object for authenticated user.
89 | - Fires unfollow_done signal
90 | """
91 | user = self.get_object()
92 |
93 | request.user.following.remove(user)
94 |
95 | unfollow_done.send(sender=self, follower=request.user, following=user)
96 |
97 | return HttpResponse(json.dumps({
98 | "success": True
99 | }))
100 |
101 | def post(self, request, **kwargs):
102 | """
103 | - Creates `FollowedProfile` object for authenticated user.
104 | - Fires follow_done signal
105 | """
106 | user = self.get_object()
107 |
108 | if user.followers.filter(pk=request.user.pk).exists():
109 | return HttpResponse(json.dumps({
110 | "error": "You already following this people."
111 | }))
112 |
113 | request.user.following.add(user)
114 |
115 | follow_done.send(sender=self, follower=request.user, following=user)
116 |
117 | return HttpResponse(json.dumps({
118 | "success": True
119 | }))
120 |
121 |
122 | class ProfileUpdateView(UpdateView):
123 | template_name = "auth/update.html"
124 | form_class = ProfileUpdateForm
125 |
126 | def get_object(self, queryset=None):
127 | return self.request.user
128 |
129 | def get_success_url(self):
130 | return reverse("auth_profile", args=[self.request.user.username])
131 |
--------------------------------------------------------------------------------
/web/templates/premises/tree_view.html:
--------------------------------------------------------------------------------
1 |
2 | {% for premise in premises %}
3 | -
4 |
5 |
6 | {{ premise.get_premise_type_display }}
7 |
8 |
9 |
10 | {{ premise.formatted_text|safe }}
11 |
12 |
13 |
14 | {% if premise.sources %}
15 |
kaynaklar:
16 |
{{ premise.sources|urlize }}
17 | {% endif %}
18 |
gönderen:
19 |
20 | {{ premise.user }}
21 |
22 | {% if premise.reports.exists %}
23 |
24 | {{ premise.reports.count }} safsata bildirimi yapıldı.
25 |
26 | {% if premise.fallacies %}
27 | {% for fallacy in premise.fallacies %}
28 |
{{ fallacy }}
29 | {% endfor %}
30 | {% endif %}
31 |
32 |
33 | {% endif %}
34 |
35 | {% if premise.supporters.exists %}
36 |
37 | {{ premise.supporters.count }} destekleyen var:
38 |
39 | {% for supporter in premise.recent_supporters %}{% if not forloop.first %}, {% endif %}
40 |
{{ supporter.username }}{% endfor %}
41 | {% if premise.supporters.count > 5 %}
42 | ve diğerleri.
43 | {% endif %}
44 |
45 |
46 | {% endif %}
47 |
48 | {% if edit_mode or user == premise.user %}
49 |
59 | {% else %}
60 | {% if user.is_authenticated %}
61 |
62 |
önerme gönder
63 | {% if premise.user != user %}
64 | {% if user in premise.supporters.all %}
65 |
69 | {% else %}
70 |
74 | {% endif %}
75 | {% endif %}
76 |
77 | {% endif %}
78 | {% endif %}
79 |
80 | {% if user.is_authenticated %}
81 |
84 | {% endif %}
85 |
86 |
87 | {% if premise.children.exists %}
88 | {% with template_name="premises/tree_view.html" premises=premise.published_children %}
89 | {% include template_name %}
90 | {% endwith %}
91 | {% endif %}
92 |
93 | {% endfor %}
94 |
--------------------------------------------------------------------------------
/web/templates/premises/contention_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load static %}
3 |
4 | {% block title %}{{ contention.title }}{% endblock %}
5 |
6 | {% block header %}
7 |
68 | {% endblock %}
69 |
70 | {% block content %}
71 |
72 |
73 |
74 | ←
75 | ↑
↓
76 | →
77 |
78 |
harita üzerinde gezinmek için yön tuşlarını kullanabilirsiniz.
79 |
80 |
81 |
82 |
83 |
84 |
85 | {{ contention }}
86 |
87 | {% if user.is_authenticated %}
88 |
önerme ekle
89 | {% endif %}
90 |
91 |
92 |
93 |
94 |
95 |
96 | -
97 |
98 | {% with template_name="premises/tree_view.html"%}
99 | {% with premises=contention.published_premises.all %}
100 | {% include template_name %}
101 | {% endwith %}
102 | {% endwith %}
103 |
104 |
105 |
106 |
107 |
108 | {% endblock %}
109 |
110 | {% block extra-scripts %}
111 |
132 | {% endblock %}
133 |
134 |
135 | {% block footer %}{% endblock %}
136 |
--------------------------------------------------------------------------------
/web/profiles/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from django.conf import settings
4 | from django.contrib.auth.models import AbstractUser
5 | from django.core.exceptions import ObjectDoesNotExist
6 | from django.db import models
7 | from django.dispatch import receiver
8 | from django.template.loader import render_to_string
9 | from premises.models import Contention, Report, Premise
10 | from premises.signals import (reported_as_fallacy, added_premise_for_premise,
11 | added_premise_for_contention, supported_a_premise)
12 | from profiles.signals import follow_done
13 |
14 |
15 | class Profile(AbstractUser):
16 | following = models.ManyToManyField("self", symmetrical=False)
17 |
18 | @property
19 | def followers(self):
20 | # todo: find a way to make reverse relationships
21 | # with symmetrical false option
22 | return Profile.objects.filter(following=self)
23 |
24 |
25 | NOTIFICATION_ADDED_PREMISE_FOR_CONTENTION = 0
26 | NOTIFICATION_ADDED_PREMISE_FOR_PREMISE = 1
27 | NOTIFICATION_REPORTED_AS_FALLACY = 2
28 | NOTIFICATION_FOLLOWED_A_PROFILE = 3
29 | NOTIFICATION_SUPPORTED_A_PREMISE = 4
30 |
31 | NOTIFICATION_TYPES = (
32 | (NOTIFICATION_ADDED_PREMISE_FOR_CONTENTION,
33 | "added-premise-for-contention"),
34 | (NOTIFICATION_ADDED_PREMISE_FOR_PREMISE,
35 | "added-premise-for-premise"),
36 | (NOTIFICATION_REPORTED_AS_FALLACY,
37 | "reported-as-fallacy"),
38 | (NOTIFICATION_FOLLOWED_A_PROFILE,
39 | "followed"),
40 | (NOTIFICATION_SUPPORTED_A_PREMISE,
41 | "supported-a-premise"),
42 | )
43 |
44 |
45 | class Notification(models.Model):
46 | # sender can be `null` for system notifications
47 | sender = models.ForeignKey(settings.AUTH_USER_MODEL,
48 | null=True, blank=True,
49 | related_name="sent_notifications")
50 | recipient = models.ForeignKey(settings.AUTH_USER_MODEL,
51 | related_name="notifications")
52 | date_created = models.DateTimeField(auto_now_add=True)
53 | notification_type = models.IntegerField(choices=NOTIFICATION_TYPES)
54 | is_read = models.BooleanField(default=False)
55 | target_object_id = models.IntegerField(null=True, blank=True)
56 |
57 | class Meta:
58 | ordering = ['is_read', '-date_created']
59 |
60 | def get_target_object(self):
61 | model = {
62 | NOTIFICATION_ADDED_PREMISE_FOR_CONTENTION: Premise,
63 | NOTIFICATION_ADDED_PREMISE_FOR_PREMISE: Premise,
64 | NOTIFICATION_REPORTED_AS_FALLACY: Report,
65 | NOTIFICATION_FOLLOWED_A_PROFILE: Profile,
66 | NOTIFICATION_SUPPORTED_A_PREMISE: Premise,
67 | }.get(self.notification_type)
68 |
69 | try:
70 | instance = model.objects.get(pk=self.target_object_id)
71 | except ObjectDoesNotExist:
72 | instance = None
73 |
74 | return instance
75 |
76 | def render(self):
77 | template_name = ("notifications/%s.html" %
78 | self.get_notification_type_display())
79 | return render_to_string(template_name, {
80 | "notification": self,
81 | "target_object": self.get_target_object()
82 | })
83 |
84 |
85 | @receiver(reported_as_fallacy)
86 | def create_fallacy_notification(sender, report, *args, **kwargs):
87 | Notification.objects.create(
88 | sender=None, # notification should be anonymous
89 | recipient=report.premise.user,
90 | notification_type=NOTIFICATION_REPORTED_AS_FALLACY,
91 | target_object_id=report.id
92 | )
93 |
94 |
95 | @receiver(added_premise_for_premise)
96 | def create_premise_answer_notification(sender, premise, *args, **kwargs):
97 | if premise.user != premise.parent.user:
98 | Notification.objects.create(
99 | sender=premise.user,
100 | recipient=premise.parent.user,
101 | notification_type=NOTIFICATION_ADDED_PREMISE_FOR_PREMISE,
102 | target_object_id=premise.id
103 | )
104 |
105 |
106 | @receiver(supported_a_premise)
107 | def create_premise_answer_notification(premise, user, *args, **kwargs):
108 | Notification.objects.create(
109 | sender=user,
110 | recipient=premise.user,
111 | notification_type=NOTIFICATION_SUPPORTED_A_PREMISE,
112 | target_object_id=premise.id
113 | )
114 |
115 |
116 | @receiver(added_premise_for_contention)
117 | def create_contention_contribution_notification(sender, premise, *args, **kwargs):
118 | if premise.user != premise.argument.user:
119 | Notification.objects.create(
120 | sender=premise.user,
121 | recipient=premise.argument.user,
122 | notification_type=NOTIFICATION_ADDED_PREMISE_FOR_CONTENTION,
123 | target_object_id=premise.id
124 | )
125 |
126 |
127 | @receiver(follow_done)
128 | def create_following_notification(following, follower, **kwargs):
129 | """
130 | Sends notification to the followed user from the follower.
131 | """
132 | Notification.objects.create(
133 | target_object_id=follower.id,
134 | notification_type=NOTIFICATION_FOLLOWED_A_PROFILE,
135 | sender=follower,
136 | recipient_id=following.id
137 | )
138 |
--------------------------------------------------------------------------------
/web/templates/tos.md:
--------------------------------------------------------------------------------
1 | ## Kullanım Koşulları
2 |
3 | Arguman.org'u ziyaretiniz ve arguman.org içeriğini kullanımınız aşağıdaki kullanım şartlarına tabidir:
4 |
5 | ### Uygunsuz İçerik
6 |
7 | arguman.org içeriği önceden kontrol edilmiyor olması nedeniyle 18 yaşından küçüklere uygun olmayabilir. çocuklarınızın gelişimini olumsuz etkileyecek içeriklerden uzak durmasını sağlayabileceğiniz filtre yazılımları bulunmaktadır, bunları kullanmanızı tavsiye ederiz. internet'in çocuklarca güvenli kullanımı konusunda bilgilendirme için: (http://www.guvenliweb.org.tr/annebabakilavuz/)
8 |
9 | ### Hukuka aykırı içerik ve şikayet
10 |
11 | arguman.org(site), arguman.org'a ait olup, 5651 sayılı kanun çerçevesinde yer sağlayıcı sıfatı’yla(yer sağlayıcı belge no:XXXX t:XX/XX/20XX) hizmet vermektedir. kullanıcılar tarafından oluşturulan içerikler herhangi bir ön incelemeye tabi olmaksızın ve doğrudan kullanıcılar tarafından yayına alınmaktadır. tarafımıza başvurulmadığı müddetçe, yayınlanan içeriğin hukuka uygunluğunu denetleme yükümlülüğümüz bulunmamaktadır. ancak, arguman.org yer sağladığı içeriğin hukuka uygunluğunu sağlamaya özen göstermekte, bu nedenle yapılan her başvuruyu dikkatle değerlendirmektedir.
12 |
13 | bir argümanda yer alan önermeler genelde farklı kullanıcılara ait olmaktadır. bu nedenle, başlıklarda yer alan her bir yazı 'şikayet' butonu kullanılarak ayrı ayrı şikayet edilebilmektedir. bunun haricinde, sitede yer alan iletişim arabiriminden bize ulaşabilirsiniz.
14 |
15 | şikayetleriniz kişisel haberleşme niteliğinde olmayıp, şikayetiniz ve iletişim bilgileriniz gerekli görüldüğü takdirde yayınlanabilir, üçüncü kişilerle ve/veya yasal mercilerle paylaşılabilir. bu nedenle şikayetlerinizde ifşa edilmesini istemediğiniz beyanlarda bulunmamanızı tavsiye ederiz.
16 |
17 | ## Kullanım
18 |
19 | sitede yer alan hizmetler ve içeriklerden şahsi kullanımınız için faydalanmanız gerekmektedir. site tarafından sunulan hizmetlerin ve arguman.org’tan yazılı izin alınmadığı müddetçe ticari amaçla kullanılması yasaktır.
20 |
21 | sitemize çeşitli yazılımlar veya aletler kullanarak; izinsiz giriş yapma, veri değiştirme veya başka işlemlerle sitenin işleyişine müdahale etme veya engelleme, sitenin işleyişini kasıtlı olarak yavaşlatma, virüs vs. zararlı içerik yüklemek suretiyle arguman.org’e zarar verme girişiminde bulunduğunuz takdirde arguman.org’un yasal yollara başvurma hakkı saklıdır.
22 |
23 |
24 | # İçerik
25 |
26 | arguman.org’te yer alan içeriğin doğru ve/veya güncel olduğu hiçbir şekilde iddia veya garanti edilmemektedir. aksine, kullanıcılar tamamen gerçekdışı içerik dahi üretebilirler. arguman.org’te yer alan içeriğe yukarıda 'hukuka aykırı içerik ve şikayet' kısmında belirtilen haller dışında müdahale edilmediğinden, arguman.org’te yer alan herhangi bir bilgi, yorum, öneri, tecrübe paylaşımı ve benzeri içeriğin referans alınması nedeniyle oluşabilecek (dolaylı veya doğrudan)maddi ve/veya manevi herhangi bir kaybınızdan sorumlu olmadığımızı belirtmek isteriz.
27 |
28 | arguman.org’te yer alan bağlantılara/yönlendirmelere (link) ilişkin hiçbir sorumluluğumuz bulunmamaktadır. arguman.org bağlantı ve yönlendirmelerin içeriğini kontrol etmediği gibi, içeriğin doğruluğu, yönlendirilen adreste virüs, trojan, phishing ve benzeri zararlı bir unsur olmadığı veya yönlendirilen sitenin hukuka uygun olduğu gibi veya benzeri hiçbir bir garanti vermemektedir.
29 |
30 | ## Telif hakları ve alıntı
31 |
32 | arguman.org bir derleme eser olup, derleme esere (içerik) ve site kodlarına ilişkin tüm haklar(kopyalama, çoğaltma, işleme, yayma) arguman.org’ye aittir. içeriğe ilişkin kullanıcılar ile arguman.org arasında yapılmış sözleşme hükümleri saklıdır.
33 |
34 | arguman.org içeriğini, içeriği değiştirmemek, ilgili kullanıcıya ve arguman.org’e aktif link vererek atıfta bulunmak ve ticari amaç gütmeden kullanmak kaydıyla alıntılamanız mümkündür ancak tüm bu kurallara uyulsa dahi alıntılama eserin, konularının veya başlıklarının tümünü kapsamayacak ve tam içeriğe ulaşmak için arguman.org'ün ziyaret edilmesi ihtiyacını ortadan kaldırmayacak düzeyde olmalıdır.
35 |
36 | aksi yönde açık yazılı izin olmadığı müddetçe site içeriğinin kısmen veya tamamen ticari amaçla ve/veya reklam ve benzeri gelir elde edecek şekilde kullanılması yasaktır.
37 |
38 | arguman.org’nin dilediği zaman, dilediği kişi veya kurumun yukarıdaki kurallara uyarak dahi alıntı yapmasını engelleme hakkı saklıdır.
39 |
40 | ## Gizlilik
41 |
42 | arguman.org’te bulunduğunuz süre boyunca 'cookie' olarak da adlandırılan çerezlerin ve buna benzer unsurların bilgisayarınıza yerleştirilmesi söz konusu olabilir. çerezler basit metin dosyalarından ibaret olup, kimlik ve sair özel bilgiler içermez, bu nevi kişisel bilgi içermemekle beraber, oturum bilgileri ve benzeri veriler saklanır ve sizi tekrar tanımak ve benzeri hizmetler için kullanılabilir. bu konuda daha fazla bilgiyi (http://www.allaboutcookies.org/) (http://en.wikipedia.org/wiki/http_cookie) ve http://tr.wikipedia.org/wiki/çerez_(internet) adreslerinden edinebilirsiniz. (verilen linklerden ulaşacağınız içeriğin güvenilirliğine ilişkin sorumluluğumuz bulunmamaktadır.)
43 |
44 | arguman.org'ü ziyaretiniz esnasında ip adresiniz ve bilgisayarınız aracılığıyla toplanabilen diğer veriler arguman.org tarafından anonim olarak kaydedilmektedir.
45 |
46 | ## arguman.org’un hak ve yükümlülükleri
47 |
48 | arguman.org kullanım koşullarını önceden bildirmeksizin değiştirebilir. bu nedenle kullanım koşullarını belirli aralıklarla incelemeniz öneririz.
49 |
50 | arguman.org, arguman.org'u oluşturan tüm unsurları önceden haber vermeksizin değiştirme, sona erdirme, yenilerini ekleme, ücretlendirme haklarını saklı tutmaktadır.
51 |
52 | arguman.org, gerekli gördüğü takdirde belli kişilerin, kurumların, ip numaralarının veya ip bloklarının arguman.org'e erişimini geçici veya kalıcı olarak durdurma hakkını saklı tutar.
53 |
54 | sitenin virüs ve sair zararlı içerik barındırmaması için özen sarf edilmekle birlikte, gelişen teknoloji, teknik sorun ve diğer nedenlerle bilgisayarınıza virüs, trojan gibi zararlı yazılımların bulaşması ihtimali bulunmaktadır. bu gibi risklere karşı antivirüs programları ve benzeri uygulamalar kullanmanızı tavsiye ederiz. sitemizde gezinmeniz dolayısıyla karşılaşabileceğiniz yazılımsal ve donanımsal dahil olmak üzere hiçbir zarara ilişkin sorumluluk kabul etmemekteyiz. bu nedenlerle sitemizden talepte bulunmayacağınızı peşinen kabul etmektesiniz.
55 |
56 | bu belge son olarak 26.10.2014 tarihinde güncellenmiştir.
--------------------------------------------------------------------------------
/web/newsfeed/models.py:
--------------------------------------------------------------------------------
1 | from bson import ObjectId
2 | from datetime import datetime
3 | from django.db.models.signals import post_delete, post_save
4 |
5 | from django.dispatch import receiver
6 | from django.template.loader import render_to_string
7 |
8 | from newsfeed.utils import get_collection
9 | from premises.models import Contention, Premise, Report
10 | from premises.signals import reported_as_fallacy, added_premise_for_premise, added_premise_for_contention
11 | from profiles.signals import follow_done, unfollow_done
12 | from newsfeed.constants import *
13 |
14 | RELATED_MODELS = {
15 | NEWS_TYPE_CONTENTION: Contention,
16 | NEWS_TYPE_PREMISE: Premise,
17 | NEWS_TYPE_FALLACY: Report
18 | }
19 |
20 |
21 | class EntryManager(object):
22 | """
23 | A manager that allows you to manage newsfeed items.
24 | """
25 |
26 | def __init__(self):
27 | self.load()
28 |
29 | def load(self):
30 | self.collection = get_collection("newsfeed")
31 |
32 | def create(self, object_id, news_type, sender, recipients=None,
33 | related_object=None, date_creation=None):
34 | """
35 | Creates newsfeed item from provided parameters
36 | """
37 |
38 | followers = sender.followers.values_list("id", flat=True)
39 | recipients = (recipients if recipients is not None
40 | else list(followers) + [sender.pk])
41 |
42 | entry_bundle = {
43 | "object_id": object_id,
44 | "news_type": news_type,
45 | "date_created": date_creation or datetime.now(),
46 | "sender": {
47 | "username": sender.username,
48 | "email": sender.email # it's required for gravatar
49 | },
50 | "recipients": recipients
51 | }
52 |
53 | # sometimes we have to create custom related object bundle.
54 | # for example: following actions. because user actions are
55 | # holding on relational database.
56 | if related_object is not None:
57 | entry_bundle["related_object"] = related_object
58 |
59 | self.collection.insert(entry_bundle)
60 |
61 | def add_to_recipients(self, following, follower):
62 | """
63 | Adds the id of follower to the recipients of followed profile's entries.
64 | """
65 | self.collection.update(
66 | {"sender.username": following.username},
67 | {"$push": {"recipients": follower.id}}, multi=True)
68 |
69 | def remove_from_recipients(self, following, follower):
70 | """
71 | Removes follower id from the recipients of followed profile's entries.
72 | """
73 | self.collection.update(
74 | {"sender.username": following.username},
75 | {"$pull": {"recipients": follower.id}}, multi=True)
76 |
77 | def delete(self, object_type, object_id):
78 | """
79 | Removes news entry from provided object type and object id.
80 | """
81 | self.collection.remove({
82 | "news_type": object_type,
83 | "object_id": object_id})
84 |
85 |
86 | class Entry(dict):
87 | """
88 | A model that wraps mongodb document for newsfeed.
89 | """
90 | objects = EntryManager()
91 |
92 | def render(self):
93 | return render_to_string(self.get_template(), {
94 | "entry": self,
95 | "related_object": self.related_object
96 | })
97 |
98 | __getattr__ = dict.get
99 |
100 | def get_template(self):
101 | return {
102 | NEWS_TYPE_CONTENTION: "newsfeed/contention.html",
103 | NEWS_TYPE_PREMISE: "newsfeed/premise.html",
104 | NEWS_TYPE_FALLACY: "newsfeed/fallacy.html",
105 | NEWS_TYPE_FOLLOWING: "newsfeed/following.html",
106 | }.get(self.news_type)
107 |
108 | def entry_class(self):
109 | return {
110 | NEWS_TYPE_CONTENTION: "contention_entry",
111 | NEWS_TYPE_PREMISE: "premise_entry",
112 | NEWS_TYPE_FALLACY: "fallacy_entry",
113 | NEWS_TYPE_FOLLOWING: "following_entry",
114 | }.get(self.news_type)
115 |
116 |
117 | @receiver(post_save, sender=Contention)
118 | def create_contention_entry(instance, created, **kwargs):
119 | """
120 | Creates news entries for contentions.
121 | """
122 | if created:
123 | Entry.objects.create(
124 | object_id=instance.id,
125 | news_type=instance.get_newsfeed_type(),
126 | sender=instance.get_actor(),
127 | related_object=instance.get_newsfeed_bundle()
128 | )
129 |
130 |
131 | @receiver(added_premise_for_contention)
132 | @receiver(added_premise_for_premise)
133 | def create_premise_entry(premise, **kwargs):
134 | """
135 | Creates news entries for the following types:
136 | - Premise
137 | - Report
138 | That models have `get_news_type` method.
139 | """
140 | Entry.objects.create(
141 | object_id=premise.id,
142 | news_type=premise.get_newsfeed_type(),
143 | sender=premise.get_actor(),
144 | related_object=premise.get_newsfeed_bundle()
145 | )
146 |
147 |
148 | @receiver(reported_as_fallacy)
149 | def create_fallacy_entry(report, **kwargs):
150 | """
151 | Creates fallacy entries.
152 | """
153 | Entry.objects.create(
154 | object_id=report.id,
155 | news_type=report.get_newsfeed_type(),
156 | sender=report.get_actor(),
157 | related_object=report.get_newsfeed_bundle()
158 | )
159 |
160 |
161 | @receiver(follow_done)
162 | def create_following_entry(follower, following, **kwargs):
163 | """
164 | Creates news entry for following actions.
165 | """
166 | Entry.objects.create(
167 | object_id=following.id,
168 | news_type=NEWS_TYPE_FOLLOWING,
169 | sender=follower,
170 | related_object=dict(username=following.username,
171 | email=following.email)
172 | )
173 |
174 |
175 | @receiver(follow_done)
176 | def add_to_recipients(follower, following, **kwargs):
177 | """
178 | Adds the entries of followed profile to follower's newsfeed.
179 | """
180 | Entry.objects.add_to_recipients(
181 | following=following, follower=follower)
182 |
183 |
184 | @receiver(unfollow_done)
185 | def remove_from_recipients(follower, following, **kwargs):
186 | """
187 | Removes the entries of unfollowed profile.
188 | """
189 | Entry.objects.remove_from_recipients(following=following,
190 | follower=follower)
191 |
192 |
193 | @receiver(post_delete, sender=Contention)
194 | @receiver(post_delete, sender=Premise)
195 | def remove_news_entry(instance, **kwargs):
196 | Entry.objects.delete(
197 | object_type=instance.get_newsfeed_type(),
198 | object_id=instance.id
199 | )
200 |
--------------------------------------------------------------------------------
/web/static/css/h5bp.css:
--------------------------------------------------------------------------------
1 | /*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */
2 |
3 | /*
4 | * What follows is the result of much research on cross-browser styling.
5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
6 | * Kroc Camen, and the H5BP dev community and team.
7 | */
8 |
9 | /* ==========================================================================
10 | Base styles: opinionated defaults
11 | ========================================================================== */
12 |
13 | html,
14 | button,
15 | input,
16 | select,
17 | textarea {
18 | color: #222;
19 | }
20 |
21 | html {
22 | font-size: 1em;
23 | line-height: 1.4;
24 | }
25 |
26 | /*
27 | * Remove text-shadow in selection highlight: h5bp.com/i
28 | * These selection rule sets have to be separate.
29 | * Customize the background color to match your design.
30 | */
31 |
32 | ::-moz-selection {
33 | background: #b3d4fc;
34 | text-shadow: none;
35 | }
36 |
37 | ::selection {
38 | background: #b3d4fc;
39 | text-shadow: none;
40 | }
41 |
42 | /*
43 | * A better looking default horizontal rule
44 | */
45 |
46 | hr {
47 | display: block;
48 | height: 1px;
49 | border: 0;
50 | border-top: 1px solid #ccc;
51 | margin: 1em 0;
52 | padding: 0;
53 | }
54 |
55 | /*
56 | * Remove the gap between images, videos, audio and canvas and the bottom of
57 | * their containers: h5bp.com/i/440
58 | */
59 |
60 | audio,
61 | canvas,
62 | img,
63 | video {
64 | vertical-align: middle;
65 | }
66 |
67 | /*
68 | * Remove default fieldset styles.
69 | */
70 |
71 | fieldset {
72 | border: 0;
73 | margin: 0;
74 | padding: 0;
75 | }
76 |
77 | /*
78 | * Allow only vertical resizing of textareas.
79 | */
80 |
81 | textarea {
82 | resize: vertical;
83 | }
84 |
85 | /* ==========================================================================
86 | Browse Happy prompt
87 | ========================================================================== */
88 |
89 | .browsehappy {
90 | margin: 0.2em 0;
91 | background: #ccc;
92 | color: #000;
93 | padding: 0.2em 0;
94 | }
95 |
96 | /* ==========================================================================
97 | Author's custom styles
98 | ========================================================================== */
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | /* ==========================================================================
117 | Helper classes
118 | ========================================================================== */
119 |
120 | /*
121 | * Image replacement
122 | */
123 |
124 | .ir {
125 | background-color: transparent;
126 | border: 0;
127 | overflow: hidden;
128 | /* IE 6/7 fallback */
129 | *text-indent: -9999px;
130 | }
131 |
132 | .ir:before {
133 | content: "";
134 | display: block;
135 | width: 0;
136 | height: 150%;
137 | }
138 |
139 | /*
140 | * Hide from both screenreaders and browsers: h5bp.com/u
141 | */
142 |
143 | .hidden {
144 | display: none !important;
145 | visibility: hidden;
146 | }
147 |
148 | /*
149 | * Hide only visually, but have it available for screenreaders: h5bp.com/v
150 | */
151 |
152 | .visuallyhidden {
153 | border: 0;
154 | clip: rect(0 0 0 0);
155 | height: 1px;
156 | margin: -1px;
157 | overflow: hidden;
158 | padding: 0;
159 | position: absolute;
160 | width: 1px;
161 | }
162 |
163 | /*
164 | * Extends the .visuallyhidden class to allow the element to be focusable
165 | * when navigated to via the keyboard: h5bp.com/p
166 | */
167 |
168 | .visuallyhidden.focusable:active,
169 | .visuallyhidden.focusable:focus {
170 | clip: auto;
171 | height: auto;
172 | margin: 0;
173 | overflow: visible;
174 | position: static;
175 | width: auto;
176 | }
177 |
178 | /*
179 | * Hide visually and from screenreaders, but maintain layout
180 | */
181 |
182 | .invisible {
183 | visibility: hidden;
184 | }
185 |
186 | /*
187 | * Clearfix: contain floats
188 | *
189 | * For modern browsers
190 | * 1. The space content is one way to avoid an Opera bug when the
191 | * `contenteditable` attribute is included anywhere else in the document.
192 | * Otherwise it causes space to appear at the top and bottom of elements
193 | * that receive the `clearfix` class.
194 | * 2. The use of `table` rather than `block` is only necessary if using
195 | * `:before` to contain the top-margins of child elements.
196 | */
197 |
198 | .clearfix:before,
199 | .clearfix:after {
200 | content: " "; /* 1 */
201 | display: table; /* 2 */
202 | }
203 |
204 | .clearfix:after {
205 | clear: both;
206 | }
207 |
208 | /*
209 | * For IE 6/7 only
210 | * Include this rule to trigger hasLayout and contain floats.
211 | */
212 |
213 | .clearfix {
214 | *zoom: 1;
215 | }
216 |
217 | /* ==========================================================================
218 | EXAMPLE Media Queries for Responsive Design.
219 | These examples override the primary ('mobile first') styles.
220 | Modify as content requires.
221 | ========================================================================== */
222 |
223 | @media only screen and (min-width: 35em) {
224 | /* Style adjustments for viewports that meet the condition */
225 | }
226 |
227 | @media print,
228 | (-o-min-device-pixel-ratio: 5/4),
229 | (-webkit-min-device-pixel-ratio: 1.25),
230 | (min-resolution: 120dpi) {
231 | /* Style adjustments for high resolution devices */
232 | }
233 |
234 | /* ==========================================================================
235 | Print styles.
236 | Inlined to avoid required HTTP connection: h5bp.com/r
237 | ========================================================================== */
238 |
239 | @media print {
240 | * {
241 | background: transparent !important;
242 | color: #000 !important; /* Black prints faster: h5bp.com/s */
243 | box-shadow: none !important;
244 | text-shadow: none !important;
245 | }
246 |
247 | a,
248 | a:visited {
249 | text-decoration: underline;
250 | }
251 |
252 | a[href]:after {
253 | content: " (" attr(href) ")";
254 | }
255 |
256 | abbr[title]:after {
257 | content: " (" attr(title) ")";
258 | }
259 |
260 | /*
261 | * Don't show links for images, or javascript/internal links
262 | */
263 |
264 | .ir a:after,
265 | a[href^="javascript:"]:after,
266 | a[href^="#"]:after {
267 | content: "";
268 | }
269 |
270 | pre,
271 | blockquote {
272 | border: 1px solid #999;
273 | page-break-inside: avoid;
274 | }
275 |
276 | thead {
277 | display: table-header-group; /* h5bp.com/t */
278 | }
279 |
280 | tr,
281 | img {
282 | page-break-inside: avoid;
283 | }
284 |
285 | img {
286 | max-width: 100% !important;
287 | }
288 |
289 | @page {
290 | margin: 0.5cm;
291 | }
292 |
293 | p,
294 | h2,
295 | h3 {
296 | orphans: 3;
297 | widows: 3;
298 | }
299 |
300 | h2,
301 | h3 {
302 | page-break-after: avoid;
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/web/static/js/main.js:
--------------------------------------------------------------------------------
1 | (function (arguman) {
2 |
3 | arguman.utils = {
4 | adder: function (a, b) {
5 | return a + b
6 | }
7 | };
8 |
9 | arguman.KeyboardManager = Class.extend({
10 | currentElement: null,
11 | init: function (options) {
12 | $.extend(this, options);
13 | this.$el = $(this.el);
14 | this.bindEvents();
15 | this.setInitial();
16 | },
17 | up: function () {
18 | this.select(
19 | this.currentElement.parent().parent(),
20 | true
21 | )
22 | },
23 | down: function () {
24 | this.select(
25 | this.currentElement.find("ul").find(".child-premise").first(),
26 | true
27 | )
28 | },
29 | left: function () {
30 | this.select(
31 | this.currentElement.prev(),
32 | true
33 | );
34 | },
35 | right: function () {
36 | this.select(
37 | this.currentElement.next(),
38 | true
39 | );
40 | },
41 | select: function (leaf, scroll) {
42 | if (leaf.is(".child-premise")) {
43 | this.$el.find(".premise").removeClass("focused");
44 | this.currentElement = leaf;
45 | leaf.find(".premise").first().addClass("focused");
46 | if (scroll) {
47 | this.scrollTo(leaf);
48 | }
49 | }
50 | },
51 | needsScroll: function () {
52 | var maxHeight = Math.max.apply(this,
53 | this.$el.find(".premise")
54 | .toArray()
55 | .map(function (el) {
56 | return $(el).offset().top + $(el).height();
57 | }));
58 |
59 | return (this.$el.width() > window.innerWidth ||
60 | maxHeight > window.innerHeight)
61 | },
62 | scrollTo: function (el) {
63 | if (this.needsScroll()) {
64 | var center = el.offset().left + (el.width()/2);
65 | $('html, body').animate({
66 | scrollTop: el.offset().top - 200,
67 | scrollLeft: center - (window.innerWidth / 2)
68 | }, 150);
69 | }
70 | },
71 | setInitial: function () {
72 | if (this.needsScroll()) {
73 | this.select(this.$el.find(".child-premise").first(), false);
74 | }
75 | },
76 | bindEvents: function () {
77 | $(document).keydown(function(e) {
78 | switch(e.which) {
79 | case 37:
80 | this.left();
81 | break;
82 |
83 | case 38:
84 | this.up();
85 | break;
86 |
87 | case 39:
88 | this.right();
89 | break;
90 |
91 | case 40:
92 | this.down();
93 | break;
94 |
95 | default: return; // exit this handler for other keys
96 | }
97 | e.preventDefault(); // prevent the default action (scroll / move caret)
98 | }.bind(this));
99 |
100 | if (this.needsScroll()) {
101 | this.$el.find(".premise-content").on('click', function (event) {
102 | this.select($(event.target).parents(".child-premise").eq(0))
103 | }.bind(this));
104 | $(this.info).show();
105 | $(window).on("scroll", function () {
106 | $(this.info).fadeOut(100);
107 | }.bind(this));
108 | } else {
109 | $(this.info).hide();
110 | }
111 |
112 | }
113 | });
114 |
115 | arguman.CollapsibleTree = Class.extend({
116 |
117 | premiseWidth: 260,
118 |
119 | init: function (options) {
120 | $.extend(this, options);
121 | this.$el = $(this.el);
122 | },
123 |
124 | setTreeWidth: function () {
125 | /*
126 | * Set full width to container, and reduce the width with
127 | * positions of last premise.
128 | * */
129 |
130 | if (this.$el.hasClass("empty")) {
131 | return;
132 | }
133 |
134 | var root = this.$el.find(".root"),
135 | mainContention = $(this.mainContention);
136 | var treeWidth = parseInt(this.$el.data("width")) * (this.premiseWidth * 2);
137 | this.width = treeWidth;
138 | this.$el.width(treeWidth);
139 |
140 | var mainPremises = root.next().children();
141 |
142 | if (mainPremises.length) {
143 | var premises = root.parent().find("li");
144 |
145 | var maxPosition = Math.max.apply(this,
146 | premises.toArray().map(function (premise) {
147 | return $(premise).offset().left
148 | }));
149 |
150 | this.width = (maxPosition + this.premiseWidth + 50);
151 | this.$el.width(this.width);
152 | mainContention.css({
153 | "margin-left": (root.position().left) - (mainContention.width() / 2)
154 | });
155 | }
156 |
157 | if (this.width < window.innerWidth) {
158 | this.$el.css({
159 | "margin-left": (window.innerWidth / 2) - (this.width / 2)
160 | });
161 | mainContention.css({
162 | "margin-left": (window.innerWidth / 2) - (mainContention.width() / 2)
163 | });
164 | }
165 |
166 | },
167 |
168 | render: function () {
169 | this.setTreeWidth();
170 | this.$el.css("visibility", "visible");
171 | }
172 | });
173 |
174 | arguman.Zoom = Class.extend({
175 | canvas: '#app',
176 | currentSize: function () {
177 | return parseFloat($(this.canvas).css('zoom')) || 1
178 | },
179 | zoomOut: function () {
180 | var current = this.currentSize();
181 | $(this.canvas).css('zoom', current - 0.1);
182 | $('#zoomIn').show();
183 | $(this.canvas).css('padding-top', function (index, curValue) {
184 | return parseInt(curValue, 10) + 40 + 'px';
185 | });
186 | },
187 | zoomIn: function () {
188 | var current = this.currentSize();
189 | $('#app').css('zoom', current + 0.1);
190 | if (parseFloat($(this.canvas).css('zoom')) >= 1) {
191 | $('#zoomIn').hide();
192 | }
193 | $(this.canvas).css('padding-top', function (index, curValue) {
194 | return parseInt(curValue, 10) - 40 + 'px';
195 | });
196 | },
197 | init: function () {
198 | $('#zoomIn').on('click', $.proxy(this, 'zoomIn'));
199 | $('#zoomOut').on('click', $.proxy(this, 'zoomOut'));
200 | }
201 | });
202 |
203 | })(window.arguman || (window.arguman = {}));
--------------------------------------------------------------------------------
/web/premises/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import json
3 | import operator
4 | import os
5 |
6 | from uuid import uuid4
7 | from django.utils.html import escape
8 | from markdown2 import markdown
9 | from unidecode import unidecode
10 |
11 | from django.core import validators
12 | from django.conf import settings
13 | from django.template.loader import render_to_string
14 | from django.db import models
15 | from django.template.defaultfilters import slugify
16 | from django.utils.encoding import smart_unicode
17 | from django.utils.functional import curry
18 | from newsfeed.constants import NEWS_TYPE_FALLACY, NEWS_TYPE_PREMISE, NEWS_TYPE_CONTENTION
19 | from premises.constants import MAX_PREMISE_CONTENT_LENGTH
20 |
21 | from premises.managers import ContentionManager, DeletePreventionManager
22 | from premises.mixins import DeletePreventionMixin
23 |
24 | OBJECTION = 0
25 | SUPPORT = 1
26 | SITUATION = 2
27 |
28 | PREMISE_TYPES = (
29 | (OBJECTION, u"ama"),
30 | (SUPPORT, u"çünkü"),
31 | (SITUATION, u"ancak"),
32 | )
33 |
34 |
35 | class Channel(models.Model):
36 | title = models.CharField(max_length=255)
37 |
38 | def __unicode__(self):
39 | return smart_unicode(self.title)
40 |
41 |
42 | class Contention(DeletePreventionMixin, models.Model):
43 | channel = models.ForeignKey(Channel, related_name='contentions',
44 | null=True, blank=True)
45 | title = models.CharField(
46 | max_length=255, verbose_name="Argüman",
47 | help_text=render_to_string("premises/examples/contention.html"))
48 | slug = models.SlugField(max_length=255, blank=True)
49 | description = models.TextField(
50 | null=True, blank=True, verbose_name="Ek bilgiler",)
51 | user = models.ForeignKey(settings.AUTH_USER_MODEL)
52 | owner = models.CharField(
53 | max_length=255, null=True, blank=True,
54 | verbose_name="Orijinal söylem",
55 | help_text=render_to_string("premises/examples/owner.html"))
56 | sources = models.TextField(
57 | null=True, blank=True,
58 | verbose_name="Kaynaklar",
59 | help_text=render_to_string("premises/examples/sources.html"))
60 | is_featured = models.BooleanField(default=False)
61 | is_published = models.BooleanField(default=True)
62 | date_creation = models.DateTimeField(auto_now_add=True)
63 | date_modification = models.DateTimeField(auto_now_add=True,
64 | auto_now=True)
65 | ip_address = models.IPAddressField(null=True, blank=True)
66 |
67 | objects = ContentionManager()
68 |
69 | class Meta:
70 | ordering = ["-date_creation"]
71 |
72 | def __unicode__(self):
73 | return smart_unicode(self.title)
74 |
75 | @models.permalink
76 | def get_absolute_url(self):
77 | return 'contention_detail', [self.slug]
78 |
79 | def get_full_url(self):
80 | return "http://%(site_url)s%(path)s" % {
81 | "site_url": settings.SITE_URL,
82 | "path": self.get_absolute_url()
83 | }
84 |
85 | def save(self, *args, **kwargs):
86 | """
87 | - Make unique slug if it is not given.
88 | """
89 | if not self.slug:
90 | slug = slugify(unidecode(self.title))
91 | duplications = Contention.objects.filter(slug=slug)
92 | if duplications.exists():
93 | self.slug = "%s-%s" % (slug, uuid4().hex)
94 | else:
95 | self.slug = slug
96 | return super(Contention, self).save(*args, **kwargs)
97 |
98 | def published_premises(self, parent=None, ignore_parent=False):
99 | premises = self.premises.filter(is_approved=True)
100 | if ignore_parent:
101 | return premises
102 | return premises.filter(parent=parent)
103 |
104 | published_children = published_premises
105 |
106 | def children_by_premise_type(self, premise_type=None, ignore_parent=False):
107 | return (self.published_premises(ignore_parent=ignore_parent)
108 | .filter(premise_type=premise_type))
109 |
110 | because = curry(children_by_premise_type,
111 | premise_type=SUPPORT, ignore_parent=True)
112 | but = curry(children_by_premise_type,
113 | premise_type=OBJECTION, ignore_parent=True)
114 | however = curry(children_by_premise_type,
115 | premise_type=SITUATION, ignore_parent=True)
116 |
117 | def update_sibling_counts(self):
118 | for premise in self.premises.filter():
119 | premise.update_sibling_counts()
120 |
121 | def last_user(self):
122 | try:
123 | # add date_creation
124 | premise = self.premises.order_by("-pk")[0]
125 | except IndexError:
126 | user = self.user
127 | else:
128 | user = premise.user
129 | return user
130 |
131 | def width(self):
132 | children = self.published_children()
133 | return children.count() + reduce(operator.add,
134 | map(operator.methodcaller("width"),
135 | children), 0)
136 |
137 | def get_actor(self):
138 | """
139 | Encapsulated for newsfeed app.
140 | """
141 | return self.user
142 |
143 | def get_newsfeed_type(self):
144 | return NEWS_TYPE_CONTENTION
145 |
146 | def get_newsfeed_bundle(self):
147 | return {
148 | "title": self.title,
149 | "owner": self.owner,
150 | "uri": self.get_absolute_url()
151 | }
152 |
153 |
154 | class Premise(DeletePreventionMixin, models.Model):
155 | argument = models.ForeignKey(Contention, related_name="premises")
156 | user = models.ForeignKey(settings.AUTH_USER_MODEL)
157 | parent = models.ForeignKey("self", related_name="children",
158 | null=True, blank=True,
159 | verbose_name="Öncülü",
160 | help_text="Önermenin öncülü. Eğer boş bırakılırsa"
161 | "ana argümanın bir önermesi olur.")
162 | premise_type = models.IntegerField(
163 | default=SUPPORT,
164 | choices=PREMISE_TYPES, verbose_name="Önerme Tipi",
165 | help_text=render_to_string("premises/examples/premise_type.html"))
166 | text = models.TextField(
167 | null=True, blank=True,
168 | verbose_name="Önermenin İçeriği",
169 | help_text=render_to_string("premises/examples/premise.html"),
170 | validators=[validators.MaxLengthValidator(MAX_PREMISE_CONTENT_LENGTH)])
171 | sources = models.TextField(
172 | null=True, blank=True, verbose_name="Kaynaklar",
173 | help_text=render_to_string("premises/examples/premise_source.html"))
174 | is_approved = models.BooleanField(default=True, verbose_name="Yayınla")
175 | collapsed = models.BooleanField(default=False)
176 | supporters = models.ManyToManyField(settings.AUTH_USER_MODEL,
177 | related_name="supporting")
178 | sibling_count = models.IntegerField(default=1) # denormalized field
179 | child_count = models.IntegerField(default=1) # denormalized field
180 | max_sibling_count = models.IntegerField(default=1) # denormalized field
181 | date_creation = models.DateTimeField(auto_now_add=True)
182 | ip_address = models.IPAddressField(null=True, blank=True)
183 |
184 | objects = DeletePreventionManager()
185 |
186 | def __unicode__(self):
187 | return smart_unicode(self.text)
188 |
189 | @models.permalink
190 | def get_absolute_url(self):
191 | return 'contention_detail', [self.argument.slug]
192 |
193 | def update_sibling_counts(self):
194 | count = self.get_siblings().count()
195 | self.get_siblings().update(sibling_count=count)
196 |
197 | def get_siblings(self):
198 | return Premise.objects.filter(parent=self.parent,
199 | argument=self.argument)
200 |
201 | def published_children(self):
202 | return self.children.filter(is_approved=True)
203 |
204 | def premise_class(self):
205 | return {
206 | OBJECTION: "but",
207 | SUPPORT: "because",
208 | SITUATION: "however"
209 | }.get(self.premise_type)
210 |
211 | def reported_by(self, user):
212 | return self.reports.filter(reporter=user).exists()
213 |
214 | def formatted_sources(self):
215 | return markdown(escape(self.sources), safe_mode=True)
216 |
217 | def formatted_text(self):
218 | return markdown(escape(self.text), safe_mode=True)
219 |
220 | def width(self):
221 | total = self.published_children().count()
222 |
223 | for child in self.published_children():
224 | total += child.width()
225 |
226 | return total
227 |
228 | def fallacies(self):
229 | fallacies = set(self.reports.values_list("fallacy_type", flat=True))
230 | mapping = dict(get_fallacy_types())
231 | fallacy_list = [mapping.get(fallacy) for fallacy in fallacies]
232 | return filter(None, fallacy_list)
233 |
234 | def get_actor(self):
235 | # Encapsulated for newsfeed app.
236 | return self.user
237 |
238 | def get_newsfeed_type(self):
239 | return NEWS_TYPE_PREMISE
240 |
241 | def get_newsfeed_bundle(self):
242 | return {
243 | "premise_type": self.premise_type,
244 | "premise_class": self.premise_class(),
245 | "text": self.text,
246 | "sources": self.sources,
247 | "contention": self.argument.get_newsfeed_bundle()
248 | }
249 |
250 | def recent_supporters(self):
251 | return self.supporters.all()[0:5]
252 |
253 | class Comment(models.Model):
254 | premise = models.ForeignKey(Premise)
255 | user = models.ForeignKey(settings.AUTH_USER_MODEL)
256 | text = models.TextField()
257 | date_creation = models.DateTimeField(auto_now_add=True)
258 | is_active = models.BooleanField(default=True)
259 |
260 | def __unicode__(self):
261 | return smart_unicode(self.text)
262 |
263 |
264 | def get_fallacy_types():
265 | if hasattr(get_fallacy_types, "cache"):
266 | return get_fallacy_types.cache
267 |
268 | get_fallacy_types.cache = json.load(
269 | open(os.path.join(os.path.dirname(__file__),
270 | "fallacies.json")))
271 |
272 | return get_fallacy_types.cache
273 |
274 |
275 | class Report(models.Model):
276 | reporter = models.ForeignKey(settings.AUTH_USER_MODEL,
277 | related_name='reports')
278 | premise = models.ForeignKey(Premise,
279 | related_name='reports',
280 | blank=True,
281 | null=True)
282 | contention = models.ForeignKey(Contention,
283 | related_name='reports',
284 | blank=True,
285 | null=True)
286 | fallacy_type = models.CharField(
287 | "Safsata Tipi", choices=get_fallacy_types(), null=True, blank=False,
288 | max_length=255, default="Wrong Direction",
289 | help_text=render_to_string("premises/examples/fallacy.html"))
290 |
291 |
292 | def __unicode__(self):
293 | return smart_unicode(self.fallacy_type)
294 |
295 | def get_actor(self):
296 | """
297 | Encapsulated for newsfeed app.
298 | """
299 | return self.reporter
300 |
301 | def get_newsfeed_type(self):
302 | return NEWS_TYPE_FALLACY
303 |
304 | def get_newsfeed_bundle(self):
305 | return {
306 | "fallacy_type": self.fallacy_type,
307 | "premise": self.premise.get_newsfeed_bundle(),
308 | "contention": self.contention.get_newsfeed_bundle()
309 | }
310 |
--------------------------------------------------------------------------------
/web/static/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */
2 |
3 | /* ==========================================================================
4 | HTML5 display definitions
5 | ========================================================================== */
6 |
7 | /**
8 | * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
9 | */
10 |
11 | article,
12 | aside,
13 | details,
14 | figcaption,
15 | figure,
16 | footer,
17 | header,
18 | hgroup,
19 | main,
20 | nav,
21 | section,
22 | summary {
23 | display: block;
24 | }
25 |
26 | /**
27 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
28 | */
29 |
30 | audio,
31 | canvas,
32 | video {
33 | display: inline-block;
34 | *display: inline;
35 | *zoom: 1;
36 | }
37 |
38 | /**
39 | * Prevent modern browsers from displaying `audio` without controls.
40 | * Remove excess height in iOS 5 devices.
41 | */
42 |
43 | audio:not([controls]) {
44 | display: none;
45 | height: 0;
46 | }
47 |
48 | /**
49 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
50 | * Known issue: no IE 6 support.
51 | */
52 |
53 | [hidden] {
54 | display: none;
55 | }
56 |
57 | /* ==========================================================================
58 | Base
59 | ========================================================================== */
60 |
61 | /**
62 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
63 | * `em` units.
64 | * 2. Prevent iOS text size adjust after orientation change, without disabling
65 | * user zoom.
66 | */
67 |
68 | html {
69 | font-size: 100%; /* 1 */
70 | -ms-text-size-adjust: 100%; /* 2 */
71 | -webkit-text-size-adjust: 100%; /* 2 */
72 | }
73 |
74 | /**
75 | * Address `font-family` inconsistency between `textarea` and other form
76 | * elements.
77 | */
78 |
79 | html,
80 | button,
81 | input,
82 | select,
83 | textarea {
84 | font-family: sans-serif;
85 | }
86 |
87 | /**
88 | * Address margins handled incorrectly in IE 6/7.
89 | */
90 |
91 | body {
92 | margin: 0;
93 | }
94 |
95 | /* ==========================================================================
96 | Links
97 | ========================================================================== */
98 |
99 | /**
100 | * Address `outline` inconsistency between Chrome and other browsers.
101 | */
102 |
103 | a:focus {
104 | outline: thin dotted;
105 | }
106 |
107 | /**
108 | * Improve readability when focused and also mouse hovered in all browsers.
109 | */
110 |
111 | a:active,
112 | a:hover {
113 | outline: 0;
114 | }
115 |
116 | /* ==========================================================================
117 | Typography
118 | ========================================================================== */
119 |
120 | /**
121 | * Address font sizes and margins set differently in IE 6/7.
122 | * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
123 | * and Chrome.
124 | */
125 |
126 | h1 {
127 | font-size: 2em;
128 | margin: 0.67em 0;
129 | }
130 |
131 | h2 {
132 | font-size: 1.5em;
133 | margin: 0.83em 0;
134 | }
135 |
136 | h3 {
137 | font-size: 1.17em;
138 | margin: 1em 0;
139 | }
140 |
141 | h4 {
142 | font-size: 1em;
143 | margin: 1.33em 0;
144 | }
145 |
146 | h5 {
147 | font-size: 0.83em;
148 | margin: 1.67em 0;
149 | }
150 |
151 | h6 {
152 | font-size: 0.67em;
153 | margin: 2.33em 0;
154 | }
155 |
156 | /**
157 | * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
158 | */
159 |
160 | abbr[title] {
161 | border-bottom: 1px dotted;
162 | }
163 |
164 | /**
165 | * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
166 | */
167 |
168 | b,
169 | strong {
170 | font-weight: bold;
171 | }
172 |
173 | blockquote {
174 | margin: 1em 40px;
175 | }
176 |
177 | /**
178 | * Address styling not present in Safari 5 and Chrome.
179 | */
180 |
181 | dfn {
182 | font-style: italic;
183 | }
184 |
185 | /**
186 | * Address differences between Firefox and other browsers.
187 | * Known issue: no IE 6/7 normalization.
188 | */
189 |
190 | hr {
191 | -moz-box-sizing: content-box;
192 | box-sizing: content-box;
193 | height: 0;
194 | }
195 |
196 | /**
197 | * Address styling not present in IE 6/7/8/9.
198 | */
199 |
200 | mark {
201 | background: #ff0;
202 | color: #000;
203 | }
204 |
205 | /**
206 | * Address margins set differently in IE 6/7.
207 | */
208 |
209 | p,
210 | pre {
211 | margin: 1em 0;
212 | }
213 |
214 | /**
215 | * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
216 | */
217 |
218 | code,
219 | kbd,
220 | pre,
221 | samp {
222 | font-family: monospace, serif;
223 | _font-family: 'courier new', monospace;
224 | font-size: 1em;
225 | }
226 |
227 | /**
228 | * Improve readability of pre-formatted text in all browsers.
229 | */
230 |
231 | pre {
232 | white-space: pre;
233 | white-space: pre-wrap;
234 | word-wrap: break-word;
235 | }
236 |
237 | /**
238 | * Address CSS quotes not supported in IE 6/7.
239 | */
240 |
241 | q {
242 | quotes: none;
243 | }
244 |
245 | /**
246 | * Address `quotes` property not supported in Safari 4.
247 | */
248 |
249 | q:before,
250 | q:after {
251 | content: '';
252 | content: none;
253 | }
254 |
255 | /**
256 | * Address inconsistent and variable font size in all browsers.
257 | */
258 |
259 | small {
260 | font-size: 80%;
261 | }
262 |
263 | /**
264 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
265 | */
266 |
267 | sub,
268 | sup {
269 | font-size: 75%;
270 | line-height: 0;
271 | position: relative;
272 | vertical-align: baseline;
273 | }
274 |
275 | sup {
276 | top: -0.5em;
277 | }
278 |
279 | sub {
280 | bottom: -0.25em;
281 | }
282 |
283 | /* ==========================================================================
284 | Lists
285 | ========================================================================== */
286 |
287 | /**
288 | * Address margins set differently in IE 6/7.
289 | */
290 |
291 | dl,
292 | menu,
293 | ol,
294 | ul {
295 | margin: 1em 0;
296 | }
297 |
298 | dd {
299 | margin: 0 0 0 40px;
300 | }
301 |
302 | /**
303 | * Address paddings set differently in IE 6/7.
304 | */
305 |
306 | menu,
307 | ol,
308 | ul {
309 | padding: 0 0 0 40px;
310 | }
311 |
312 | /**
313 | * Correct list images handled incorrectly in IE 7.
314 | */
315 |
316 | nav ul,
317 | nav ol {
318 | list-style: none;
319 | list-style-image: none;
320 | }
321 |
322 | /* ==========================================================================
323 | Embedded content
324 | ========================================================================== */
325 |
326 | /**
327 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
328 | * 2. Improve image quality when scaled in IE 7.
329 | */
330 |
331 | img {
332 | border: 0; /* 1 */
333 | -ms-interpolation-mode: bicubic; /* 2 */
334 | }
335 |
336 | /**
337 | * Correct overflow displayed oddly in IE 9.
338 | */
339 |
340 | svg:not(:root) {
341 | overflow: hidden;
342 | }
343 |
344 | /* ==========================================================================
345 | Figures
346 | ========================================================================== */
347 |
348 | /**
349 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
350 | */
351 |
352 | figure {
353 | margin: 0;
354 | }
355 |
356 | /* ==========================================================================
357 | Forms
358 | ========================================================================== */
359 |
360 | /**
361 | * Correct margin displayed oddly in IE 6/7.
362 | */
363 |
364 | form {
365 | margin: 0;
366 | }
367 |
368 | /**
369 | * Define consistent border, margin, and padding.
370 | */
371 |
372 | fieldset {
373 | border: 1px solid #c0c0c0;
374 | margin: 0 2px;
375 | padding: 0.35em 0.625em 0.75em;
376 | }
377 |
378 | /**
379 | * 1. Correct color not being inherited in IE 6/7/8/9.
380 | * 2. Correct text not wrapping in Firefox 3.
381 | * 3. Correct alignment displayed oddly in IE 6/7.
382 | */
383 |
384 | legend {
385 | border: 0; /* 1 */
386 | padding: 0;
387 | white-space: normal; /* 2 */
388 | *margin-left: -7px; /* 3 */
389 | }
390 |
391 | /**
392 | * 1. Correct font size not being inherited in all browsers.
393 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
394 | * and Chrome.
395 | * 3. Improve appearance and consistency in all browsers.
396 | */
397 |
398 | button,
399 | input,
400 | select,
401 | textarea {
402 | font-size: 100%; /* 1 */
403 | margin: 0; /* 2 */
404 | vertical-align: baseline; /* 3 */
405 | *vertical-align: middle; /* 3 */
406 | }
407 |
408 | /**
409 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in
410 | * the UA stylesheet.
411 | */
412 |
413 | button,
414 | input {
415 | line-height: normal;
416 | }
417 |
418 | /**
419 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
420 | * All other form control elements do not inherit `text-transform` values.
421 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
422 | * Correct `select` style inheritance in Firefox 4+ and Opera.
423 | */
424 |
425 | button,
426 | select {
427 | text-transform: none;
428 | }
429 |
430 | /**
431 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
432 | * and `video` controls.
433 | * 2. Correct inability to style clickable `input` types in iOS.
434 | * 3. Improve usability and consistency of cursor style between image-type
435 | * `input` and others.
436 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
437 | * Known issue: inner spacing remains in IE 6.
438 | */
439 |
440 | button,
441 | html input[type="button"], /* 1 */
442 | input[type="reset"],
443 | input[type="submit"] {
444 | -webkit-appearance: button; /* 2 */
445 | cursor: pointer; /* 3 */
446 | *overflow: visible; /* 4 */
447 | }
448 |
449 | /**
450 | * Re-set default cursor for disabled elements.
451 | */
452 |
453 | button[disabled],
454 | html input[disabled] {
455 | cursor: default;
456 | }
457 |
458 | /**
459 | * 1. Address box sizing set to content-box in IE 8/9.
460 | * 2. Remove excess padding in IE 8/9.
461 | * 3. Remove excess padding in IE 7.
462 | * Known issue: excess padding remains in IE 6.
463 | */
464 |
465 | input[type="checkbox"],
466 | input[type="radio"] {
467 | box-sizing: border-box; /* 1 */
468 | padding: 0; /* 2 */
469 | *height: 13px; /* 3 */
470 | *width: 13px; /* 3 */
471 | }
472 |
473 | /**
474 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
475 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
476 | * (include `-moz` to future-proof).
477 | */
478 |
479 | input[type="search"] {
480 | -webkit-appearance: textfield; /* 1 */
481 | -moz-box-sizing: content-box;
482 | -webkit-box-sizing: content-box; /* 2 */
483 | box-sizing: content-box;
484 | }
485 |
486 | /**
487 | * Remove inner padding and search cancel button in Safari 5 and Chrome
488 | * on OS X.
489 | */
490 |
491 | input[type="search"]::-webkit-search-cancel-button,
492 | input[type="search"]::-webkit-search-decoration {
493 | -webkit-appearance: none;
494 | }
495 |
496 | /**
497 | * Remove inner padding and border in Firefox 3+.
498 | */
499 |
500 | button::-moz-focus-inner,
501 | input::-moz-focus-inner {
502 | border: 0;
503 | padding: 0;
504 | }
505 |
506 | /**
507 | * 1. Remove default vertical scrollbar in IE 6/7/8/9.
508 | * 2. Improve readability and alignment in all browsers.
509 | */
510 |
511 | textarea {
512 | overflow: auto; /* 1 */
513 | vertical-align: top; /* 2 */
514 | }
515 |
516 | /* ==========================================================================
517 | Tables
518 | ========================================================================== */
519 |
520 | /**
521 | * Remove most spacing between table cells.
522 | */
523 |
524 | table {
525 | border-collapse: collapse;
526 | border-spacing: 0;
527 | }
528 |
--------------------------------------------------------------------------------
/web/premises/views.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 |
3 | import json
4 | from datetime import timedelta
5 | from markdown2 import markdown
6 |
7 | from django.contrib import messages
8 | from django.core.urlresolvers import reverse
9 | from django.utils import timezone
10 | from django.db.models import Max
11 | from django.utils.timezone import now
12 | from django.http import HttpResponse
13 | from django.shortcuts import get_object_or_404, redirect
14 | from django.template.loader import render_to_string
15 | from django.views.generic import DetailView, TemplateView, CreateView, View
16 | from django.views.generic.edit import UpdateView
17 | from django.db.models import Count
18 |
19 | from blog.models import Post
20 | from premises.utils import int_or_zero
21 | from premises.models import Contention, Premise
22 | from premises.forms import (ArgumentCreationForm, PremiseCreationForm,
23 | PremiseEditForm, ReportForm)
24 | from premises.signals import (added_premise_for_premise,
25 | added_premise_for_contention, reported_as_fallacy,
26 | supported_a_premise)
27 |
28 |
29 | class ContentionDetailView(DetailView):
30 | template_name = "premises/contention_detail.html"
31 | model = Contention
32 |
33 | def get_context_data(self, **kwargs):
34 | contention = self.get_object()
35 | view = ("list-view" if self.request.GET.get("view") == "list"
36 | else "tree-view")
37 | edit_mode = (
38 | self.request.user.is_superuser or
39 | self.request.user.is_staff or
40 | contention.user == self.request.user)
41 | return super(ContentionDetailView, self).get_context_data(
42 | view=view,
43 | path=contention.get_absolute_url(),
44 | edit_mode=edit_mode,
45 | **kwargs)
46 |
47 |
48 | class ContentionJsonView(DetailView):
49 | model = Contention
50 |
51 | def render_to_response(self, context, **response_kwargs):
52 | contention = self.get_object(self.get_queryset())
53 | return HttpResponse(json.dumps({
54 | "nodes": self.build_tree(contention, self.request.user),
55 | }), content_type="application/json")
56 |
57 | def build_tree(self, contention, user):
58 | return {
59 | "name": contention.title,
60 | "parent": None,
61 | "pk": contention.pk,
62 | "owner": contention.owner,
63 | "sources": contention.sources,
64 | "is_singular": self.is_singular(contention),
65 | "children": self.get_premises(contention, user)
66 | }
67 |
68 | def get_premises(self, contention, user, parent=None):
69 | children = [{
70 | "pk": premise.pk,
71 | "name": premise.text,
72 | "parent": parent.text if parent else None,
73 | "reportable_by_authenticated_user": self.user_can_report(premise, user),
74 | "report_count": premise.reports.count(),
75 | "user": {
76 | "id": premise.user.id,
77 | "username": premise.user.username,
78 | "absolute_url": reverse("auth_profile",
79 | args=[premise.user.username])
80 | },
81 | "sources": premise.sources,
82 | "premise_type": premise.premise_class(),
83 | "children": (self.get_premises(contention, user, parent=premise)
84 | if premise.published_children().exists() else [])
85 | } for premise in contention.published_premises(parent)]
86 | return children
87 |
88 | def user_can_report(self, premise, user):
89 | if user.is_authenticated() and user != premise.user:
90 | return not premise.reported_by(user)
91 |
92 | return False
93 |
94 | def is_singular(self, contention):
95 | result = (contention
96 | .premises
97 | .all()
98 | .aggregate(max_sibling=Max('sibling_count')))
99 | return result['max_sibling'] <= 1
100 |
101 |
102 | class HomeView(TemplateView):
103 | template_name = "index.html"
104 | tab_class = "featured"
105 |
106 | paginate_by = 20
107 |
108 | def get_context_data(self, **kwargs):
109 | contentions = self.get_contentions()
110 | if self.request.user.is_authenticated():
111 | notifications_qs = self.get_unread_notifications()
112 | notifications = list(notifications_qs)
113 | self.mark_as_read(notifications_qs)
114 | else:
115 | notifications = None
116 | return super(HomeView, self).get_context_data(
117 | next_page_url=self.get_next_page_url(),
118 | tab_class=self.tab_class,
119 | notifications=notifications,
120 | has_next_page=self.has_next_page(),
121 | announcements=self.get_announcements(),
122 | contentions=contentions, **kwargs)
123 |
124 | def get_announcements(self):
125 | return Post.objects.filter(is_announcement=True)
126 |
127 | def get_offset(self):
128 | return int_or_zero(self.request.GET.get("offset"))
129 |
130 | def get_limit(self):
131 | return self.get_offset() + self.paginate_by
132 |
133 | def has_next_page(self):
134 | total = self.get_contentions(paginate=False).count()
135 | return total > (self.get_offset() + self.paginate_by)
136 |
137 | def get_next_page_url(self):
138 | offset = self.get_offset() + self.paginate_by
139 | return '?offset=%(offset)s' % {
140 | "offset": offset
141 | }
142 |
143 | def get_unread_notifications(self):
144 | return (self.request.user
145 | .notifications
146 | .filter(is_read=False)
147 | [:5])
148 |
149 | def mark_as_read(self, notifications):
150 | pks = notifications.values_list("id", flat=True)
151 | (self.request.user
152 | .notifications
153 | .filter(id__in=pks)
154 | .update(is_read=True))
155 |
156 | def get_contentions(self, paginate=True):
157 | contentions = (Contention
158 | .objects
159 | .featured())
160 |
161 | if paginate:
162 | contentions = (contentions[self.get_offset(): self.get_limit()])
163 |
164 | return contentions
165 |
166 |
167 | class NotificationsView(HomeView):
168 | template_name = "notifications.html"
169 |
170 | def get_context_data(self, **kwargs):
171 | notifications_qs = self.request.user.notifications.all()[:40]
172 | notifications = list(notifications_qs)
173 | self.mark_as_read(notifications_qs)
174 | return super(HomeView, self).get_context_data(
175 | notifications=notifications,
176 | **kwargs)
177 |
178 |
179 | class SearchView(HomeView):
180 | tab_class = 'search'
181 |
182 | def get_context_data(self, **kwargs):
183 | return super(SearchView, self).get_context_data(
184 | keywords=self.get_keywords(),
185 | **kwargs
186 | )
187 |
188 | def get_keywords(self):
189 | return self.request.GET.get('keywords') or ""
190 |
191 | def get_next_page_url(self):
192 | offset = self.get_offset() + self.paginate_by
193 | return '?offset=%(offset)s&keywords=%(keywords)s' % {
194 | "offset": offset,
195 | "keywords": self.get_keywords()
196 | }
197 |
198 |
199 | def get_contentions(self, paginate=True):
200 | keywords = self.request.GET.get('keywords')
201 | if not keywords or len(keywords) < 2:
202 | result = Contention.objects.none()
203 | else:
204 | result = (Contention
205 | .objects
206 | .filter(title__icontains=keywords))
207 |
208 | if paginate:
209 | result = result[self.get_offset():self.get_limit()]
210 |
211 | return result
212 |
213 |
214 | class NewsView(HomeView):
215 | tab_class = "news"
216 |
217 | def get_contentions(self, paginate=True):
218 | contentions = Contention.objects.filter(
219 | is_published=True)
220 |
221 | if paginate:
222 | contentions = contentions[self.get_offset():self.get_limit()]
223 |
224 | return contentions
225 |
226 |
227 | class UpdatedArgumentsView(HomeView):
228 | tab_class = "updated"
229 |
230 | def get_contentions(self, paginate=True):
231 | contentions = (Contention
232 | .objects
233 | .filter(is_published=True)
234 | .order_by('-date_modification'))
235 |
236 | if paginate:
237 | contentions = contentions[self.get_offset():self.get_limit()]
238 |
239 | return contentions
240 |
241 |
242 | class ControversialArgumentsView(HomeView):
243 | tab_class = "controversial"
244 |
245 | def get_contentions(self, paginate=True):
246 | last_week = now() - timedelta(days=3)
247 | contentions = (Contention
248 | .objects
249 | .annotate(num_children=Count('premises'))
250 | .order_by('-num_children')
251 | .filter(date_modification__gte=last_week))
252 | if paginate:
253 | return contentions[self.get_offset():self.get_limit()]
254 |
255 | return contentions
256 |
257 |
258 | class AboutView(TemplateView):
259 | template_name = "about.html"
260 |
261 | def get_context_data(self, **kwargs):
262 | content = markdown(render_to_string("about.md"))
263 | return super(AboutView, self).get_context_data(
264 | content=content, **kwargs)
265 |
266 | class TosView(TemplateView):
267 | template_name = "tos.html"
268 |
269 | def get_context_data(self, **kwargs):
270 | content = markdown(render_to_string("tos.md"))
271 | return super(TosView, self).get_context_data(
272 | content=content, **kwargs)
273 |
274 |
275 | class ArgumentCreationView(CreateView):
276 | template_name = "premises/new_contention.html"
277 | form_class = ArgumentCreationForm
278 |
279 | def form_valid(self, form):
280 | form.instance.user = self.request.user
281 | form.instance.ip_address = self.request.META['REMOTE_ADDR']
282 | response = super(ArgumentCreationView, self).form_valid(form)
283 | form.instance.update_sibling_counts()
284 | return response
285 |
286 |
287 | class ArgumentUpdateView(UpdateView):
288 | template_name = "premises/edit_contention.html"
289 | form_class = ArgumentCreationForm
290 |
291 | def get_queryset(self):
292 | contentions = Contention.objects.all()
293 | if self.request.user.is_superuser:
294 | return contentions
295 | return contentions.filter(user=self.request.user)
296 |
297 | def form_valid(self, form):
298 | form.instance.user = self.request.user
299 | response = super(ArgumentUpdateView, self).form_valid(form)
300 | form.instance.update_sibling_counts()
301 | return response
302 |
303 |
304 | class ArgumentPublishView(DetailView):
305 |
306 | def get_queryset(self):
307 | return Contention.objects.filter(user=self.request.user)
308 |
309 | def post(self, request, slug):
310 | contention = self.get_object()
311 | contention.is_published = True
312 | contention.save()
313 | messages.info(request, u"Argüman yayına alındı.")
314 | return redirect(contention)
315 |
316 |
317 | class ArgumentUnpublishView(DetailView):
318 |
319 | def get_queryset(self):
320 | return Contention.objects.filter(user=self.request.user)
321 |
322 | def post(self, request, slug):
323 | contention = self.get_object()
324 | contention.is_published = False
325 | contention.save()
326 | messages.info(request, u"Argüman yayından kaldırıldı.")
327 | return redirect(contention)
328 |
329 |
330 | class ArgumentDeleteView(DetailView):
331 |
332 | def get_queryset(self):
333 | return Contention.objects.filter(user=self.request.user)
334 |
335 | def post(self, request, slug):
336 | contention = self.get_object()
337 | contention.delete()
338 | messages.info(request, u"Argümanınız silindi.")
339 | return redirect("home")
340 |
341 | delete = post
342 |
343 |
344 | class PremiseEditView(UpdateView):
345 | template_name = "premises/edit_premise.html"
346 | form_class = PremiseEditForm
347 |
348 | def get_queryset(self):
349 | premises = Premise.objects.all()
350 | if self.request.user.is_superuser:
351 | return premises
352 | return premises.filter(user=self.request.user)
353 |
354 | def form_valid(self, form):
355 | response = super(PremiseEditView, self).form_valid(form)
356 | form.instance.argument.update_sibling_counts()
357 | return response
358 |
359 | def get_context_data(self, **kwargs):
360 | return super(PremiseEditView, self).get_context_data(
361 | #contention=self.get_contention(),
362 | **kwargs)
363 |
364 |
365 | class PremiseCreationView(CreateView):
366 | template_name = "premises/new_premise.html"
367 | form_class = PremiseCreationForm
368 |
369 | def get_context_data(self, **kwargs):
370 | return super(PremiseCreationView, self).get_context_data(
371 | contention=self.get_contention(),
372 | parent=self.get_parent(),
373 | **kwargs)
374 |
375 | def form_valid(self, form):
376 | contention = self.get_contention()
377 | form.instance.user = self.request.user
378 | form.instance.argument = contention
379 | form.instance.parent = self.get_parent()
380 | form.instance.is_approved = True
381 | form.instance.ip_address = self.request.META['REMOTE_ADDR']
382 | form.save()
383 | contention.update_sibling_counts()
384 |
385 | if form.instance.parent:
386 | added_premise_for_premise.send(sender=self,
387 | premise=form.instance)
388 | else:
389 | added_premise_for_contention.send(sender=self,
390 | premise=form.instance)
391 |
392 | contention.date_modification = timezone.now()
393 | contention.save()
394 |
395 | return redirect(contention)
396 |
397 | def get_contention(self):
398 | return get_object_or_404(Contention, slug=self.kwargs['slug'])
399 |
400 | def get_parent(self):
401 | parent_pk = self.kwargs.get("pk")
402 | if parent_pk:
403 | return get_object_or_404(Premise, pk=parent_pk)
404 |
405 | class PremiseSupportView(View):
406 | def get_premise(self):
407 | premises = Premise.objects.exclude(user=self.request.user)
408 | return get_object_or_404(premises, pk=self.kwargs['pk'])
409 |
410 | def post(self, request, *args, **kwargs):
411 | premise = self.get_premise()
412 | premise.supporters.add(self.request.user)
413 | supported_a_premise.send(sender=self, premise=premise,
414 | user=self.request.user)
415 | return redirect(self.get_contention())
416 |
417 | def get_contention(self):
418 | return get_object_or_404(Contention, slug=self.kwargs['slug'])
419 |
420 |
421 | class PremiseUnsupportView(PremiseSupportView):
422 | def delete(self, request, *args, **kwargs):
423 | premise = self.get_premise()
424 | premise.supporters.remove(self.request.user)
425 | return redirect(self.get_contention())
426 |
427 | post = delete
428 |
429 |
430 | class PremiseDeleteView(View):
431 | def get_premise(self):
432 | if self.request.user.is_staff:
433 | premises = Premise.objects.all()
434 | else:
435 | premises = Premise.objects.filter(user=self.request.user)
436 | return get_object_or_404(premises,
437 | pk=self.kwargs['pk'])
438 |
439 | def delete(self, request, *args, **kwargs):
440 | contention = self.get_premise()
441 | contention.delete()
442 | contention.update_sibling_counts()
443 | return redirect(self.get_contention())
444 |
445 | post = delete
446 |
447 | def get_contention(self):
448 | return get_object_or_404(Contention, slug=self.kwargs['slug'])
449 |
450 |
451 | class ReportView(CreateView):
452 | form_class = ReportForm
453 | template_name = "premises/report.html"
454 |
455 | def get_context_data(self, **kwargs):
456 | return super(ReportView, self).get_context_data(
457 | premise=self.get_premise(),
458 | **kwargs)
459 |
460 | def get_contention(self):
461 | return get_object_or_404(Contention, slug=self.kwargs['slug'])
462 |
463 | def get_premise(self):
464 | return get_object_or_404(Premise, pk=self.kwargs['pk'])
465 |
466 | def form_valid(self, form):
467 | contention = self.get_contention()
468 | premise = self.get_premise()
469 | form.instance.contention = contention
470 | form.instance.premise = premise
471 | form.instance.reporter = self.request.user
472 | form.save()
473 | reported_as_fallacy.send(sender=self, report=form.instance)
474 | return redirect(contention)
475 |
--------------------------------------------------------------------------------