From 44e5201d831a4d87271f49fdbefe70a08d431f5a Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 23 Jun 2020 13:00:30 +0500 Subject: [PATCH 1/2] Replace DataTables with backend solutions --- contributors/forms/__init__.py | 1 + contributors/forms/forms.py | 28 ++++++ contributors/models/contributor.py | 9 +- contributors/urls.py | 4 +- contributors/utils/misc.py | 16 +++ contributors/utils/mixins.py | 69 +++++++++++++ contributors/views/contributors.py | 18 +++- contributors/views/contributors_for_month.py | 7 +- contributors/views/organization.py | 43 ++++---- contributors/views/organizations.py | 14 ++- contributors/views/repositories.py | 20 +++- contributors/views/repository.py | 39 ++------ locale/ru/LC_MESSAGES/django.mo | Bin 5779 -> 6084 bytes locale/ru/LC_MESSAGES/django.po | 93 +++++++++++------- setup.cfg | 4 +- static/css/base.css | 8 +- static/js/scripts.js | 26 ----- templates/base.html | 2 - templates/components/pagination.html | 37 +++++++ .../components/sort_and_search_form.html | 3 + .../components/tables/contributors_list.html | 6 +- .../components/tables/list_as_table.html | 2 +- .../tables/organization_details.html | 27 ----- .../components/tables/organizations_list.html | 6 +- .../components/tables/repositories_list.html | 24 +++-- .../components/tables/repository_details.html | 35 ------- templates/contributors_for_month.html | 2 + templates/contributors_list.html | 2 + templates/organization_details.html | 4 +- templates/organizations_list.html | 2 + templates/repositories_list.html | 2 + templates/repository_details.html | 4 +- 32 files changed, 336 insertions(+), 221 deletions(-) create mode 100644 contributors/forms/forms.py create mode 100644 contributors/utils/mixins.py create mode 100644 templates/components/pagination.html create mode 100644 templates/components/sort_and_search_form.html delete mode 100644 templates/components/tables/organization_details.html delete mode 100644 templates/components/tables/repository_details.html diff --git a/contributors/forms/__init__.py b/contributors/forms/__init__.py index 63d84655..1c60e439 100644 --- a/contributors/forms/__init__.py +++ b/contributors/forms/__init__.py @@ -1 +1,2 @@ from contributors.forms.admin_forms import OrgNamesForm, RepoNamesForm +from contributors.forms.forms import ListSortAndSearchForm diff --git a/contributors/forms/forms.py b/contributors/forms/forms.py new file mode 100644 index 00000000..25b9bdf1 --- /dev/null +++ b/contributors/forms/forms.py @@ -0,0 +1,28 @@ +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Submit +from django import forms +from django.utils.translation import gettext_lazy as _ + +from contributors.utils.misc import prepare_choices + + +class ListSortAndSearchForm(forms.Form): + """A form for sort and search.""" + + sort = forms.ChoiceField(label=_("Sort by"), required=False) + descending = forms.BooleanField( + label=_("Descending"), initial=False, required=False, + ) + search = forms.CharField(label=_("Search"), required=False) + page = forms.IntegerField( + widget=forms.HiddenInput(), initial=1, required=False, + ) + + def __init__(self, sortable_fields, *args, **kwargs): + """Initialize the form.""" + super().__init__(*args, **kwargs) + self.fields['sort'].choices = prepare_choices(sortable_fields) + self.helper = FormHelper() + self.helper.form_method = 'get' + self.helper.form_class = 'form-inline my-3' + self.helper.add_input(Submit('', _("Apply"))) diff --git a/contributors/models/contributor.py b/contributors/models/contributor.py index fe7ad8f9..e9a74cd3 100644 --- a/contributors/models/contributor.py +++ b/contributors/models/contributor.py @@ -9,11 +9,14 @@ class ContributorQuerySet(models.QuerySet): - """Custom contributor QuerySet.""" + """A custom contributor QuerySet.""" def visible(self): """Return only visible contributors.""" - return self.filter(is_visible=True) + return self.filter( + is_visible=True, + contribution__repository__is_visible=True, + ) def with_contributions(self): """Return a list of contributors annotated with contributions.""" @@ -36,7 +39,7 @@ def for_month(self): class Contributor(CommonFields): - """Model representing a contributor.""" + """A model representing a contributor.""" login = models.CharField(_("login"), max_length=NAME_LENGTH) avatar_url = models.URLField(_("avatar URL")) diff --git a/contributors/urls.py b/contributors/urls.py index 5a7eeda7..bc7e1aac 100644 --- a/contributors/urls.py +++ b/contributors/urls.py @@ -12,7 +12,7 @@ ), path( 'organizations/', - views.organization.DetailView.as_view(), + views.organization.OrgRepositoryList.as_view(), name='organization_details', ), path( @@ -22,7 +22,7 @@ ), path( 'repositories/', - views.repository.DetailView.as_view(), + views.repository.RepoContributorList.as_view(), name='repository_details', ), path( diff --git a/contributors/utils/misc.py b/contributors/utils/misc.py index efe6c852..e09d9f32 100644 --- a/contributors/utils/misc.py +++ b/contributors/utils/misc.py @@ -7,6 +7,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils import timezone +from django.utils.translation import gettext_lazy as trans NUM_OF_MONTHS_IN_A_YEAR = 12 @@ -129,3 +130,18 @@ def datetime_month_ago(): """Return datetime 1 month ago from now.""" dt_now = timezone.now() return dt_now - relativedelta.relativedelta(months=1) + + +def prepare_choices(collection): + """Return a collection of 2-tuples to use as choices.""" + normalized_items = [] + for col_item in collection: + if isinstance(col_item, str): + normalized_items.append( + (col_item, trans(col_item.replace('_', ' ').capitalize())), + ) + elif isinstance(col_item, tuple): + normalized_items.append(col_item) + else: + raise TypeError("Unknown item type") + return normalized_items diff --git a/contributors/utils/mixins.py b/contributors/utils/mixins.py new file mode 100644 index 00000000..b777145c --- /dev/null +++ b/contributors/utils/mixins.py @@ -0,0 +1,69 @@ +from contextlib import suppress +from functools import reduce +from operator import __or__ + +from django.core.paginator import Paginator +from django.db.models import Q # noqa: WPS347 +from django.views.generic.list import MultipleObjectMixin + +from contributors.forms import ListSortAndSearchForm + + +def get_page_range(page_obj): + """ + Return a range of page numbers to display. + + The first and last 5 pages are visible when the current page is among them. + 3 page numbers are displayed in other cases. + """ + index = page_obj.number - 1 + max_index = page_obj.paginator.num_pages + if index < 4: + start_index = 0 + end_index = 5 + elif index <= max_index - 5: + start_index = index - 1 + end_index = index + 2 + else: + start_index = max_index - 5 + end_index = max_index + return page_obj.paginator.page_range[start_index:end_index] + + +class FilteringAndPaginationMixin(MultipleObjectMixin): + """A mixin for filtering and pagination.""" + + paginate_by = 25 + + def get_adjusted_queryset(self): + """Return a sorted and filtered QuerySet.""" + self.ordering = self.request.GET.get('sort', self.get_ordering()) + filter_value = self.request.GET.get('search', '').strip() + lookups = {} + for field in self.searchable_fields: + key = '{0}{1}'.format(field, '__icontains') + lookups[key] = filter_value + expressions = [Q(**{key: value}) for key, value in lookups.items()] # noqa: WPS110,E501 + direction = '-' if self.request.GET.get('descending', False) else '' + return self.get_queryset().filter( + reduce(__or__, expressions), + ).order_by('{0}{1}'.format(direction, self.get_ordering())) + + def get_context_data(self, **kwargs): + """Add context.""" + context = super().get_context_data(**kwargs) + + paginator = Paginator(self.get_adjusted_queryset(), self.paginate_by) + page_obj = paginator.get_page(self.request.GET.get('page')) + + form = ListSortAndSearchForm(self.sortable_fields, self.request.GET) + + get_params = self.request.GET.copy() + with suppress(KeyError): + get_params.pop('page') + + context['page_obj'] = page_obj + context['page_range'] = get_page_range(page_obj) + context['form'] = form + context['get_params'] = get_params.urlencode() + return context diff --git a/contributors/views/contributors.py b/contributors/views/contributors.py index f11ba936..8bd34cb4 100644 --- a/contributors/views/contributors.py +++ b/contributors/views/contributors.py @@ -1,11 +1,23 @@ from django.views import generic -from contributors.models.contributor import Contributor +from contributors.models import Contributor +from contributors.utils.mixins import FilteringAndPaginationMixin -class ListView(generic.ListView): +class ListView(FilteringAndPaginationMixin, generic.ListView): """A list of contributors with contributions.""" queryset = Contributor.objects.visible().with_contributions() template_name = 'contributors_list.html' - context_object_name = 'contributors_list' + sortable_fields = ( + 'login', + 'name', + 'commits', + 'additions', + 'deletions', + 'pull_requests', + 'issues', + 'comments', + ) + searchable_fields = ('login', 'name') + ordering = sortable_fields[0] diff --git a/contributors/views/contributors_for_month.py b/contributors/views/contributors_for_month.py index f2a8c286..f2d5345a 100644 --- a/contributors/views/contributors_for_month.py +++ b/contributors/views/contributors_for_month.py @@ -1,10 +1,9 @@ -from django.views import generic - -from contributors.models.contributor import Contributor +from contributors.models import Contributor from contributors.utils import misc +from contributors.views import contributors -class ListView(generic.ListView): +class ListView(contributors.ListView): """A list of contributors with monthly contributions.""" template_name = 'contributors_for_month.html' diff --git a/contributors/views/organization.py b/contributors/views/organization.py index 17747bc0..f4a62205 100644 --- a/contributors/views/organization.py +++ b/contributors/views/organization.py @@ -1,35 +1,28 @@ -from django.db.models import Count, Q # noqa: WPS347 -from django.views import generic +from django.utils.translation import gettext_lazy as _ from contributors.models import Organization +from contributors.views import repositories -class DetailView(generic.DetailView): - """Organization's details.""" +class OrgRepositoryList(repositories.ListView): + """An organization's details.""" - model = Organization template_name = 'organization_details.html' + sortable_fields = ( # noqa: WPS317 + 'name', + ('project__name', _("Project")), + 'pull_requests', + 'issues', + ('contributors_count', _("Contributors")), + ) + + def get_queryset(self): + """Get a dataset.""" + self.organization = Organization.objects.get(pk=self.kwargs['pk']) + return super().get_queryset().filter(organization=self.organization) def get_context_data(self, **kwargs): - """Add additional context for the organization.""" + """Add context.""" context = super().get_context_data(**kwargs) - - repositories = ( - self.object.repository_set.filter(is_visible=True).filter( - Q(contribution__contributor__is_visible=True) - | Q(contributors__isnull=True), - ).annotate( - pull_requests=Count( - 'contribution', filter=Q(contribution__type='pr'), - ), - issues=Count( - 'contribution', filter=Q(contribution__type='iss'), - ), - contributors_count=Count( - 'contribution__contributor', distinct=True, - ), - ) - ) - - context['repositories'] = repositories + context['organization'] = self.organization return context diff --git a/contributors/views/organizations.py b/contributors/views/organizations.py index 12dadb68..2e4de534 100644 --- a/contributors/views/organizations.py +++ b/contributors/views/organizations.py @@ -1,15 +1,21 @@ from django.db.models import Count +from django.utils.translation import gettext_lazy as _ from django.views import generic from contributors.models import Organization +from contributors.utils.mixins import FilteringAndPaginationMixin -class ListView(generic.ListView): - """A view for a list of organizations.""" +class ListView(FilteringAndPaginationMixin, generic.ListView): + """A list of organizations.""" queryset = Organization.objects.filter( repository__is_visible=True, ).annotate(repository_count=Count('repository')) - template_name = 'organizations_list.html' - context_object_name = 'organizations_list' + sortable_fields = ( + 'name', + ('repository_count', _("Repositories")), + ) + searchable_fields = ('name',) + ordering = sortable_fields[0] diff --git a/contributors/views/repositories.py b/contributors/views/repositories.py index 9fbe9d87..81d00499 100644 --- a/contributors/views/repositories.py +++ b/contributors/views/repositories.py @@ -1,16 +1,19 @@ from django.db.models import Count, Q # noqa: WPS347 +from django.utils.translation import gettext_lazy as _ from django.views import generic from contributors.models import Repository +from contributors.utils.mixins import FilteringAndPaginationMixin -class ListView(generic.ListView): - """A view for a list of repositories.""" +class ListView(FilteringAndPaginationMixin, generic.ListView): + """A list of repositories.""" queryset = ( Repository.objects.select_related('organization').filter( + Q(contribution__contributor__is_visible=True) + | Q(contributors__isnull=True), is_visible=True, - contribution__contributor__is_visible=True, ).annotate( pull_requests=Count( 'contribution', filter=Q(contribution__type='pr'), @@ -24,4 +27,13 @@ class ListView(generic.ListView): ) ) template_name = 'repositories_list.html' - context_object_name = 'repositories_list' + sortable_fields = ( # noqa: WPS317 + 'name', + ('organization__name', _("Organization")), + ('project__name', _("Project")), + 'pull_requests', + 'issues', + ('contributors_count', _("Contributors")), + ) + searchable_fields = ('name', 'organization__name', 'project__name') + ordering = sortable_fields[0] diff --git a/contributors/views/repository.py b/contributors/views/repository.py index 3fd06d00..29698a23 100644 --- a/contributors/views/repository.py +++ b/contributors/views/repository.py @@ -1,38 +1,19 @@ -from django.db.models import Count, Q, Sum # noqa: WPS347 -from django.db.models.functions import Coalesce -from django.views import generic - from contributors.models import Repository +from contributors.views import contributors -class DetailView(generic.DetailView): - """Repository's details.""" +class RepoContributorList(contributors.ListView): + """A repository's details.""" - model = Repository template_name = 'repository_details.html' + def get_queryset(self): + """Get a dataset.""" + self.repository = Repository.objects.get(pk=self.kwargs['pk']) + return self.repository.contributors.visible().with_contributions() + def get_context_data(self, **kwargs): - """Add additional context for the repository.""" + """Add context.""" context = super().get_context_data(**kwargs) - - contributors = self.object.contributors.filter( - is_visible=True, - ).annotate( - pull_requests=Count( - 'contribution', filter=Q(contribution__type='pr'), - ), - issues=Count( - 'contribution', filter=Q(contribution__type='iss'), - ), - comments=Count( - 'contribution', filter=Q(contribution__type='cnt'), - ), - commits=Count( - 'contribution', filter=Q(contribution__type='cit'), - ), - additions=Coalesce(Sum('contribution__stats__additions'), 0), - deletions=Coalesce(Sum('contribution__stats__deletions'), 0), - ) - - context['contributors'] = contributors + context['repository'] = self.repository return context diff --git a/locale/ru/LC_MESSAGES/django.mo b/locale/ru/LC_MESSAGES/django.mo index 895f24d917d958b85db23b474ca7c6295335b793..351533eee6df240c2fde1a203cc96794a387d987 100644 GIT binary patch delta 2267 zcmYk-e@vBC9LMo<@!|y}>|U${18-d^!pN{xL^6M1`4bZ92W4x7lsE=@K_|I&QKnf$ zn?5<6%PFhb4{`%_WBIjoErtFBvs-6!pNMyWv~RqWlvs#6K_xi+NfNEZ}rIE=ASXx^e?f zrW|$koyenRzpFoh>c|mH*HBN8QO{3e2>V?*fwxhVHGM_y+2^0KL_V z@^LywP%o-P&1{vs-hgU%3kEgSJKcr1P%rSDCy>LO;-nD`qZ%AVb!ZH8@iNZDUr;j= z;GzaJ9knzCNFPlExy6(shbiY2Q$-aS-B5#SpdR(2Etr~0RL44<`%okBki+!3`ctlc z1l8U}^wX@m0h0X|am|{+P@jO%qm!qb94RV+| zX9KF;R#dy4&KT;sy{PtkvzdQY9Hip1k4=cvD7Uh(_hL6{bDqR|@EmF+S5Pnh3#;%Z zF2f~LC+_b?s?1Tm13$)m9K{m+4(DTfjQQ4-S8~#g>u@(VqBhOX$h{^lIPrCaFpqKt z7GWK7i+K(8d=IK)eW)4u#5v?Vk6O~NQ3Hxyau@!=Fctry8VoZ`jl9gc5@%3;3B$Mx z^@8`X96v-R&-{pw;Wg9@%w~t)hZXn$HsCDmLE4X*fr*S6bB?1LzK$BH*5fY7M>RAT zS7AMB*T0YY+%7o3M-FqH6MZmQOiKY4pk`c(uN6A zpHeCLGD0JKp3ssgY3+sQ#oUv z!WAyB9lMUu^v!d1k7F^hmMC#`;!&5^+G_eW_4+X@Bo-65ruh`st3uPsJ~B@b%L%0- zFBr&O^)R_|LMyhKs3o+m^mQxgcTh=NPiZCLCl;wddX{*GsPeW2%F~x_-P+RWeH8dA ztE#EJv8i=abL*>KOGYwO-L$jARJV0(rXu`G+xFH?US;Om$xS<&+qSoRdovGbH#I~X zH=7M@(T?!T@w}`V{`}*~E<0?$utWBo9Zv2^zHRIQd)6jw-0R9Ii?`%NeAABGL^#=P z2a`Sav>iy@_)ffcs^=?tm*QET9WwS9kH0}S&duC1q|<=?+o6*B9e%LzQ5~Vh=%h&pP4!5%*>gYb0V`sh2HBx?0HA3 zC9;UlVa|EjFq{+VaGY}qcm!2-663KM$6^b{;tkBhb}YbWsQZ2+hZ{~o?Ihz!Oh=yg zTrL?sP+~Xi!bOz#VG6cceFyp}-?#d1EBE0D>c3h2PvmfM^rm`0Y5?h|7YL#ToQDCu z>NGM*RFt7cwg!{27Bzr+4B$b`z$R3KS5Pz7hUzeadaesovDfN9quL!XV-lU?aDGln zn(9n4qp<*kSb}=fI@Cz_qDH&(q+Cv zPa}RuMh*S23qA^}PeC=9g}R>4Ni#9W>MKwKt41DmHCEn<9BvOMy}$wU5NZGosP>za zn17A%A{Cm#D|W+m^ERsC2dIv^%pTNpFHs%zTlp>a_?-KKX?l)cCgBUzX8naZn991U zzqu*Q|4K5YRFq>Q>c$9ixL!_U@B@xVH;VlNnYaKeQB!>!8}T%1Pxz@M>k6>|%Wxd- z#ys?p!?h}-4mwaH>O{TqbF_@$z7i2;6$;`y5ScAE`fNJ=fdB=Q;>D0eRJ?CTRuE8m&&3gd#m9&^QQA-g)9&s;m z0)9eX+;fRsRfE~6H7!PUybLwsZKyY@xB3(2MN~)asHJ&?n&LO8nfYxd(~I`NM5Ir* z9Cf`0)AarCA)_fjhFXFq)Ks5AP0$eYNXp+^qYjbK+;}%297# zfg0#`RQm@pgZ|x7GCtHs)D2pOY(lfLfrue$2{vzZCRU-ohAKi0Zzj~BmSY*Aq=C)!dWEI*6 zN`=JgXm0Sd;Gev*d>NjbOGeYKG-r_L1}V3^SYmlibEV~Hn%doyi3&oW)=<)>R7xPU z-r6f#W__LuiNU1#C(R<(MstIkWGVSlE9-OnKTW4%Ho?ld6~sDXBB2eer2V0!e}K|* zB9@q^0%;>rtxTvtt~eBokA%L&KM8dwtPABOo(n%t%!mztPpR{T>-}eap`1WN_\n" "Language-Team: LANGUAGE \n" @@ -57,6 +57,22 @@ msgstr "Неверное имя: " msgid "Uncheck those you wish to skip." msgstr "Снимите выбор тех, что нужно пропустить." +#: contributors/forms/forms.py +msgid "Sort by" +msgstr "Сортировка по" + +#: contributors/forms/forms.py +msgid "Descending" +msgstr "По убыванию" + +#: contributors/forms/forms.py +msgid "Search" +msgstr "Поиск" + +#: contributors/forms/forms.py +msgid "Apply" +msgstr "Применить" + #: contributors/models/base.py msgid "name" msgstr "Имя" @@ -181,6 +197,34 @@ msgstr "репозитории" msgid "Processing configuration" msgstr "Конфигурирование сбора данных" +#: contributors/views/organization.py contributors/views/repositories.py +#: templates/components/navbar.html +#: templates/components/tables/project_details.html +#: templates/components/tables/repositories_list.html +#: templates/contributors_for_month.html templates/contributors_list.html +#: templates/projects_list.html templates/repository_details.html +msgid "Contributors" +msgstr "Контрибьюторы" + +#: contributors/views/organizations.py templates/components/navbar.html +#: templates/components/tables/organizations_list.html +#: templates/organization_details.html templates/repositories_list.html +msgid "Repositories" +msgstr "Репозитории" + +#: contributors/views/repositories.py +#: templates/components/tables/project_details.html +#: templates/components/tables/repositories_list.html +#: templates/repository_details.html +msgid "Organization" +msgstr "Организация" + +#: contributors/views/repositories.py +#: templates/components/tables/repositories_list.html +#: templates/project_details.html templates/repository_details.html +msgid "Project" +msgstr "Проект" + #: templates/admin/configuration.html msgid "Get repositories" msgstr "Получить репозитории" @@ -262,21 +306,6 @@ msgstr "Блог" msgid "Projects" msgstr "Проекты" -#: templates/components/navbar.html -#: templates/components/tables/organizations_list.html -#: templates/organization_details.html templates/repositories_list.html -msgid "Repositories" -msgstr "Репозитории" - -#: templates/components/navbar.html -#: templates/components/tables/organization_details.html -#: templates/components/tables/project_details.html -#: templates/components/tables/repositories_list.html -#: templates/contributors_for_month.html templates/contributors_list.html -#: templates/projects_list.html templates/repository_details.html -msgid "Contributors" -msgstr "Контрибьюторы" - #: templates/components/navbar.html msgid "For month" msgstr "За месяц" @@ -297,80 +326,72 @@ msgstr "Администрирование" msgid "Log out" msgstr "Выйти" +#: templates/components/pagination.html +msgid "Previous" +msgstr "Назад" + +#: templates/components/pagination.html +msgid "Next" +msgstr "Далее" + #: templates/components/tables/contributors_list.html #: templates/components/tables/recent_contributors.html -#: templates/components/tables/repository_details.html msgid "Login" msgstr "Логин" #: templates/components/tables/contributors_list.html -#: templates/components/tables/organization_details.html #: templates/components/tables/organizations_list.html #: templates/components/tables/project_details.html #: templates/components/tables/recent_contributors.html #: templates/components/tables/repositories_list.html -#: templates/components/tables/repository_details.html msgid "Name" msgstr "Имя" #: templates/components/tables/contributors_list.html #: templates/components/tables/recent_contributors.html -#: templates/components/tables/repository_details.html #: templates/contributor_details.html msgid "Commits" msgstr "Коммиты" #: templates/components/tables/contributors_list.html #: templates/components/tables/recent_contributors.html -#: templates/components/tables/repository_details.html #: templates/contributor_details.html msgid "Additions" msgstr "Добавления" #: templates/components/tables/contributors_list.html #: templates/components/tables/recent_contributors.html -#: templates/components/tables/repository_details.html #: templates/contributor_details.html msgid "Deletions" msgstr "Удаления" #: templates/components/tables/contributors_list.html -#: templates/components/tables/organization_details.html #: templates/components/tables/project_details.html #: templates/components/tables/recent_contributors.html #: templates/components/tables/repositories_list.html -#: templates/components/tables/repository_details.html #: templates/contributor_details.html templates/projects_list.html msgid "Pull requests" msgstr "Запросы на включение" #: templates/components/tables/contributors_list.html -#: templates/components/tables/organization_details.html #: templates/components/tables/project_details.html #: templates/components/tables/recent_contributors.html #: templates/components/tables/repositories_list.html -#: templates/components/tables/repository_details.html #: templates/contributor_details.html templates/projects_list.html msgid "Issues" msgstr "Проблемы" #: templates/components/tables/contributors_list.html #: templates/components/tables/recent_contributors.html -#: templates/components/tables/repository_details.html #: templates/contributor_details.html msgid "Comments" msgstr "Комментарии" -#: templates/components/tables/project_details.html -#: templates/components/tables/repositories_list.html -#: templates/repository_details.html -msgid "Organization" -msgstr "Организация" - +#: templates/components/tables/contributors_list.html +#: templates/components/tables/organizations_list.html #: templates/components/tables/repositories_list.html -#: templates/project_details.html templates/repository_details.html -msgid "Project" -msgstr "Проект" +msgid "Nothing found" +msgstr "Ничего не найдено" #: templates/components/time_note.html msgid "since" diff --git a/setup.cfg b/setup.cfg index 879a2bf4..119b531c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,8 +28,6 @@ exclude = __pycache__ migrations .venv - .eggs - *.egg # Ignore some checks for Django's standard files: per-file-ignores = @@ -47,7 +45,7 @@ per-file-ignores = # imported but unused F401 - github_lib.py, github_webhook.py: + github_lib.py, github_webhook.py, misc.py: # Found too many module members WPS202, # Found line with high Jones Complexity diff --git a/static/css/base.css b/static/css/base.css index 4685c433..da8c3565 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -11,10 +11,6 @@ main { flex-grow: 1; } -table.dataTable, table.dataTable th, table.dataTable td { - box-sizing: border-box; -} - .time-note { display: inline-block; cursor: default; @@ -27,6 +23,10 @@ table.dataTable, table.dataTable th, table.dataTable td { text-decoration: none; } +.form-inline .form-group { + margin-right: 1rem; +} + .top-10 { min-width: 340px; } diff --git a/static/js/scripts.js b/static/js/scripts.js index ef08a1da..899e9303 100644 --- a/static/js/scripts.js +++ b/static/js/scripts.js @@ -1,29 +1,3 @@ -const langURLs = { - "ru": "//cdn.datatables.net/plug-ins/1.10.20/i18n/Russian.json", - "en": "", -} - -// Make tables interactive -$(document).ready(function () { - const dt = $('#list').DataTable({ - columnDefs:[{ - "targets": 0, - "orderable": false, - }], - "pageLength": 25, - "order": [], - "language": { - "url": langURLs[document.documentElement.lang] - }, - }); - - dt.on('order', (e, settings) => { - dt.column(0).nodes().each((cell, i) => { - cell.innerText = i + 1; - }); - }).draw(); -}); - // Activate Bootstrap tooltips $(document).ready(function () { $('[data-toggle="tooltip"]').tooltip(); diff --git a/templates/base.html b/templates/base.html index ef2a9a35..5844f7a8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,7 +6,6 @@ - @@ -40,7 +39,6 @@

{% block header %}{% endblock %}

- {% if GTM_ID %} diff --git a/templates/components/pagination.html b/templates/components/pagination.html new file mode 100644 index 00000000..b823575b --- /dev/null +++ b/templates/components/pagination.html @@ -0,0 +1,37 @@ +{% load i18n %} + +{% if page_obj.has_other_pages %} + +{% endif %} diff --git a/templates/components/sort_and_search_form.html b/templates/components/sort_and_search_form.html new file mode 100644 index 00000000..75272438 --- /dev/null +++ b/templates/components/sort_and_search_form.html @@ -0,0 +1,3 @@ +{% load crispy_forms_tags %} + +{% crispy form %} diff --git a/templates/components/tables/contributors_list.html b/templates/components/tables/contributors_list.html index 86461381..8bc8c0d5 100644 --- a/templates/components/tables/contributors_list.html +++ b/templates/components/tables/contributors_list.html @@ -15,9 +15,9 @@ {% endblock thead %} {% block tbody %} - {% for contributor in contributors_list %} + {% for contributor in page_obj %} - + {{ forloop.counter0|add:page_obj.start_index }} {{ contributor.login }} @@ -31,5 +31,7 @@ {{ contributor.issues }} {{ contributor.comments }} + {% empty %} + {% trans "Nothing found" %} {% endfor %} {% endblock tbody %} diff --git a/templates/components/tables/list_as_table.html b/templates/components/tables/list_as_table.html index fe2c9714..9075012d 100644 --- a/templates/components/tables/list_as_table.html +++ b/templates/components/tables/list_as_table.html @@ -1,5 +1,5 @@
- +
{% block thead %}{% endblock %} diff --git a/templates/components/tables/organization_details.html b/templates/components/tables/organization_details.html deleted file mode 100644 index f32a6c4a..00000000 --- a/templates/components/tables/organization_details.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends './list_as_table.html' %} -{% load i18n %} - -{% block thead %} - - - - - - - -{% endblock thead %} -{% block tbody %} - {% for repository in repositories %} - - - - - - - - {% endfor %} -{% endblock tbody %} diff --git a/templates/components/tables/organizations_list.html b/templates/components/tables/organizations_list.html index c97f0a90..147842ff 100644 --- a/templates/components/tables/organizations_list.html +++ b/templates/components/tables/organizations_list.html @@ -9,9 +9,9 @@ {% endblock thead %} {% block tbody %} - {% for organization in organizations_list %} + {% for organization in page_obj %} - + + {% empty %} + {% endfor %} {% endblock tbody %} diff --git a/templates/components/tables/repositories_list.html b/templates/components/tables/repositories_list.html index 3a76454b..46a82093 100644 --- a/templates/components/tables/repositories_list.html +++ b/templates/components/tables/repositories_list.html @@ -5,7 +5,9 @@ - + {% if not organization %} + + {% endif %} @@ -13,23 +15,31 @@ {% endblock thead %} {% block tbody %} - {% for repository in repositories_list %} + {% for repository in page_obj %} - + - + {% if not organization %} + + {% endif %} + {% empty %} + {% endfor %} {% endblock tbody %} diff --git a/templates/components/tables/repository_details.html b/templates/components/tables/repository_details.html deleted file mode 100644 index 217688d5..00000000 --- a/templates/components/tables/repository_details.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends './list_as_table.html' %} -{% load i18n %} - -{% block thead %} - - - - - - - - - - - -{% endblock thead %} -{% block tbody %} - {% for contributor in contributors %} - - - - - - - - - - - - {% endfor %} -{% endblock tbody %} diff --git a/templates/contributors_for_month.html b/templates/contributors_for_month.html index 61da1f64..df123737 100644 --- a/templates/contributors_for_month.html +++ b/templates/contributors_for_month.html @@ -7,5 +7,7 @@ {% include 'components/time_note.html' %} {% endblock %} {% block content %} + {% include 'components/sort_and_search_form.html' %} {% include 'components/tables/contributors_list.html' %} + {% include 'components/pagination.html' %} {% endblock content %} diff --git a/templates/contributors_list.html b/templates/contributors_list.html index 21b9bfa7..32981b6f 100644 --- a/templates/contributors_list.html +++ b/templates/contributors_list.html @@ -4,5 +4,7 @@ {% block title %}{% trans "Contributors" %}{% endblock %} {% block header %}{% trans "Contributors" %}{% endblock %} {% block content %} + {% include 'components/sort_and_search_form.html' %} {% include 'components/tables/contributors_list.html' %} + {% include 'components/pagination.html' %} {% endblock content %} diff --git a/templates/organization_details.html b/templates/organization_details.html index 845302e2..8b43bf0d 100644 --- a/templates/organization_details.html +++ b/templates/organization_details.html @@ -10,5 +10,7 @@

{% trans "Repositories" %}

{% endblock additional %} {% block content %} - {% include 'components/tables/organization_details.html' %} + {% include 'components/sort_and_search_form.html' %} + {% include 'components/tables/repositories_list.html' %} + {% include 'components/pagination.html' %} {% endblock content %} diff --git a/templates/organizations_list.html b/templates/organizations_list.html index e722be96..b9f12869 100644 --- a/templates/organizations_list.html +++ b/templates/organizations_list.html @@ -4,5 +4,7 @@ {% block title %}{% trans "Organizations" %}{% endblock %} {% block header %}{% trans "Organizations" %}{% endblock %} {% block content %} + {% include 'components/sort_and_search_form.html' %} {% include 'components/tables/organizations_list.html' %} + {% include 'components/pagination.html' %} {% endblock content %} diff --git a/templates/repositories_list.html b/templates/repositories_list.html index 59050290..77fc4f67 100644 --- a/templates/repositories_list.html +++ b/templates/repositories_list.html @@ -4,5 +4,7 @@ {% block title %}{% trans "Repositories" %}{% endblock %} {% block header %}{% trans "Repositories" %}{% endblock %} {% block content %} + {% include 'components/sort_and_search_form.html' %} {% include 'components/tables/repositories_list.html' %} + {% include 'components/pagination.html' %} {% endblock content %} diff --git a/templates/repository_details.html b/templates/repository_details.html index f45fd61b..e822ed66 100644 --- a/templates/repository_details.html +++ b/templates/repository_details.html @@ -18,5 +18,7 @@

{% trans "Contributors" %}

{% endblock additional %} {% block content %} - {% include 'components/tables/repository_details.html' %} + {% include 'components/sort_and_search_form.html' %} + {% include 'components/tables/contributors_list.html' %} + {% include 'components/pagination.html' %} {% endblock content %} From 0cc2b943f6c3ad961361de5ae1cecc44fb287870 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 24 Jun 2020 12:27:48 +0500 Subject: [PATCH 2/2] Make separate filtering and pagination mixins --- contributors/utils/mixins.py | 29 +++++++++++++++++++++-------- contributors/views/contributors.py | 4 ++-- contributors/views/organizations.py | 4 ++-- contributors/views/repositories.py | 4 ++-- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/contributors/utils/mixins.py b/contributors/utils/mixins.py index b777145c..3ff0c83a 100644 --- a/contributors/utils/mixins.py +++ b/contributors/utils/mixins.py @@ -30,11 +30,26 @@ def get_page_range(page_obj): return page_obj.paginator.page_range[start_index:end_index] -class FilteringAndPaginationMixin(MultipleObjectMixin): - """A mixin for filtering and pagination.""" +class PaginationMixin(MultipleObjectMixin): + """A mixin for pagination.""" paginate_by = 25 + def get_context_data(self, **kwargs): + """Add context.""" + context = super().get_context_data(**kwargs) + + paginator = Paginator(self.get_adjusted_queryset(), self.paginate_by) + page_obj = paginator.get_page(self.request.GET.get('page')) + + context['page_obj'] = page_obj + context['page_range'] = get_page_range(page_obj) + return context + + +class TableControlsMixin(object): + """A mixin for table controls.""" + def get_adjusted_queryset(self): """Return a sorted and filtered QuerySet.""" self.ordering = self.request.GET.get('sort', self.get_ordering()) @@ -53,17 +68,15 @@ def get_context_data(self, **kwargs): """Add context.""" context = super().get_context_data(**kwargs) - paginator = Paginator(self.get_adjusted_queryset(), self.paginate_by) - page_obj = paginator.get_page(self.request.GET.get('page')) - form = ListSortAndSearchForm(self.sortable_fields, self.request.GET) - get_params = self.request.GET.copy() with suppress(KeyError): get_params.pop('page') - context['page_obj'] = page_obj - context['page_range'] = get_page_range(page_obj) context['form'] = form context['get_params'] = get_params.urlencode() return context + + +class TableControlsAndPaginationMixin(TableControlsMixin, PaginationMixin): + """Combine mixins for table controls and pagination.""" diff --git a/contributors/views/contributors.py b/contributors/views/contributors.py index 8bd34cb4..be9340d9 100644 --- a/contributors/views/contributors.py +++ b/contributors/views/contributors.py @@ -1,10 +1,10 @@ from django.views import generic from contributors.models import Contributor -from contributors.utils.mixins import FilteringAndPaginationMixin +from contributors.utils.mixins import TableControlsAndPaginationMixin -class ListView(FilteringAndPaginationMixin, generic.ListView): +class ListView(TableControlsAndPaginationMixin, generic.ListView): """A list of contributors with contributions.""" queryset = Contributor.objects.visible().with_contributions() diff --git a/contributors/views/organizations.py b/contributors/views/organizations.py index 2e4de534..580c5d61 100644 --- a/contributors/views/organizations.py +++ b/contributors/views/organizations.py @@ -3,10 +3,10 @@ from django.views import generic from contributors.models import Organization -from contributors.utils.mixins import FilteringAndPaginationMixin +from contributors.utils.mixins import TableControlsAndPaginationMixin -class ListView(FilteringAndPaginationMixin, generic.ListView): +class ListView(TableControlsAndPaginationMixin, generic.ListView): """A list of organizations.""" queryset = Organization.objects.filter( diff --git a/contributors/views/repositories.py b/contributors/views/repositories.py index 81d00499..48e7a4de 100644 --- a/contributors/views/repositories.py +++ b/contributors/views/repositories.py @@ -3,10 +3,10 @@ from django.views import generic from contributors.models import Repository -from contributors.utils.mixins import FilteringAndPaginationMixin +from contributors.utils.mixins import TableControlsAndPaginationMixin -class ListView(FilteringAndPaginationMixin, generic.ListView): +class ListView(TableControlsAndPaginationMixin, generic.ListView): """A list of repositories.""" queryset = (
#{% trans "Name" %}{% trans "Pull requests" %}{% trans "Issues" %}{% trans "Contributors" %}
- - {{ repository.name }} - - {{ repository.pull_requests }}{{ repository.issues }}{{ repository.contributors_count }}
{{ forloop.counter0|add:page_obj.start_index }} {{ organization.name }} @@ -19,5 +19,7 @@ {{ organization.repository_count }}
{% trans "Nothing found" %}
# {% trans "Name" %}{% trans "Organization" %}{% trans "Organization" %}{% trans "Project" %} {% trans "Pull requests" %} {% trans "Issues" %}
{{ forloop.counter0|add:page_obj.start_index }} {{ repository.name }} {{ repository.organization.name }}{{ repository.organization.name }} - - {{ repository.project.name }} - + {% if repository.project %} + + {{ repository.project.name }} + + {% else %} + — + {% endif %} {{ repository.pull_requests }} {{ repository.issues }} {{ repository.contributors_count }}
{% trans "Nothing found" %}
#{% trans "Login" %}{% trans "Name" %}{% trans "Commits" %}{% trans "Additions" %}{% trans "Deletions" %}{% trans "Pull requests" %}{% trans "Issues" %}{% trans "Comments" %}
- - {{ contributor.login }} - - {% firstof contributor.name "—" %}{{ contributor.commits }}{{ contributor.additions }}{{ contributor.deletions }}{{ contributor.pull_requests }}{{ contributor.issues }}{{ contributor.comments }}