diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a3c8195a..8b3fe090 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -7,11 +7,11 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: 3.8.x - - uses: actions/cache@v2 + python-version: 3.10.x + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} diff --git a/Makefile b/Makefile index 5fca3745..ffaf1250 100644 --- a/Makefile +++ b/Makefile @@ -94,7 +94,7 @@ transcompile: uv run django-admin compilemessages load-dump: - psql -h $(DB_HOST) -U $(DB_USER) -d $(DB_NAME) -p $(DB_PORT) -f dump.sql + psql -h $(DB_HOST) -U $(DB_USER) -d $(DB_NAME) -p $(DB_PORT) -f dump.sql # Need to have GNU gettext installed transprepare: uv run django-admin makemessages --locale ru --add-location file diff --git a/auth/tests.py b/auth/tests.py index 3e106f2f..a77acca4 100644 --- a/auth/tests.py +++ b/auth/tests.py @@ -128,8 +128,8 @@ def setUp(self): """Create a test client.""" self.client: Client = Client() - @patch('contributors.utils.github_lib.get_access_token', lambda *args: None) # noqa: E501 - @patch('contributors.utils.github_lib.get_data_of_token_holder', lambda *args: None) # noqa: E501 + @patch('contributors.utils.github_lib.get_access_token', lambda *args: None) + @patch('contributors.utils.github_lib.get_data_of_token_holder', lambda *args: None) @patch('auth.backends.GitHubBackend.authenticate', lambda *args: None) def test_github_auth_view(self): """Send a request without authentication and check the response.""" diff --git a/contributors/models/__init__.py b/contributors/models/__init__.py index d0e88467..73d2fd5b 100644 --- a/contributors/models/__init__.py +++ b/contributors/models/__init__.py @@ -8,3 +8,16 @@ from contributors.models.organization import Organization from contributors.models.project import Project from contributors.models.repository import Repository + +__all__ = [ + 'CommonFields', + 'CommitStats', + 'Contribution', + 'ContributionLabel', + 'Contributor', + 'IssueInfo', + 'Label', + 'Organization', + 'Project', + 'Repository', +] diff --git a/contributors/templatetags/contrib_extras.py b/contributors/templatetags/contrib_extras.py index 4e75b2d4..93b39c6d 100644 --- a/contributors/templatetags/contrib_extras.py +++ b/contributors/templatetags/contrib_extras.py @@ -110,3 +110,13 @@ def get_canonical_url(context): if request: return request.build_absolute_uri(request.path) return '' + + +@register.simple_tag +def calc_percent_achievement(numerator, denominator): + """Get contributor statistics and required quantity.""" + if numerator is not None: + if numerator / denominator > 1: + return 100.0 + return numerator / denominator * 100 + return 0 diff --git a/contributors/tests/test_contributors_views.py b/contributors/tests/test_contributors_views.py index 0976f8ce..720f86b5 100644 --- a/contributors/tests/test_contributors_views.py +++ b/contributors/tests/test_contributors_views.py @@ -10,6 +10,8 @@ EXPECTED_CONTRIBUTORS_ISSUE_COUNT = 2 EXPECTED_CONTRIBUTORS_PR_COUNT = 2 +TEST_CONTRIBUTORS = ["mintough57", "kinganduld", "indecing", "pilly1964", "suir1948"] + class TestContributorDetailView(TestCase): """Test the methods for the contributor's details view.""" @@ -149,3 +151,11 @@ def test_get_context_data(self): self.assertEqual(response.context['contributors_issues_gte_1'], 2) self.assertEqual(response.context['contributors_comments_gte_1'], 0) self.assertEqual(response.context['contributors_editions_gte_1'], 0) + + def test_get_context_data_contributor(self): + for contributor in TEST_CONTRIBUTORS: + response = self.client.get(reverse( + 'contributors:contributor_achievements', + args=[contributor]), + ) + self.assertEqual(response.status_code, HTTPStatus.OK) diff --git a/contributors/urls.py b/contributors/urls.py index c07f16ff..6295c037 100644 --- a/contributors/urls.py +++ b/contributors/urls.py @@ -106,6 +106,11 @@ views.achievements.AchievementListView.as_view(), name='achievements', ), + path( + 'contributor_achievements/', + views.contributor_achievements.ContributorAchievementListView.as_view(), + name='contributor_achievements', + ), path( 'landing/', views.landing.LandingView.as_view(), diff --git a/contributors/utils/github_lib.py b/contributors/utils/github_lib.py index 9a3d70aa..4bcc882c 100644 --- a/contributors/utils/github_lib.py +++ b/contributors/utils/github_lib.py @@ -7,7 +7,7 @@ from django.conf import settings GITHUB_API_URL = 'https://api.github.com' -GITHUB_TOKEN_PROVIDER_URL = 'https://github.com/login/oauth/access_token' # noqa: E501,S105 +GITHUB_TOKEN_PROVIDER_URL = 'https://github.com/login/oauth/access_token' # noqa: S105 def merge_dicts(*dicts): diff --git a/contributors/views/__init__.py b/contributors/views/__init__.py index c9536b85..94d37ac1 100644 --- a/contributors/views/__init__.py +++ b/contributors/views/__init__.py @@ -2,6 +2,7 @@ about, achievements, config, + contributor_achievements, contributor_compare, filters, home, diff --git a/contributors/views/achievements.py b/contributors/views/achievements.py index 5feeff50..ee816e07 100644 --- a/contributors/views/achievements.py +++ b/contributors/views/achievements.py @@ -11,6 +11,7 @@ class AchievementListView(generic.ListView): contributors = Contributor.objects.with_contributions() pull_request_ranges_for_achievements = [100, 50, 25, 10, 1] + commit_ranges_for_achievements = [200, 100, 50, 25, 1] issue_ranges_for_achievements = [50, 25, 10, 5, 1] comment_ranges_for_achievements = [200, 100, 50, 25, 1] diff --git a/contributors/views/contributor_achievements.py b/contributors/views/contributor_achievements.py new file mode 100644 index 00000000..593f5aa2 --- /dev/null +++ b/contributors/views/contributor_achievements.py @@ -0,0 +1,292 @@ +from django.db import models +from django.db.models.functions import Coalesce +from django.views import generic + +from contributors.models import Contributor, Repository + +ID = 'id' + + +class ContributorAchievementListView(generic.ListView): + """Achievement list.""" + + template_name = 'contributor/contributor_achievements_list.html' + model = Contributor + contributors = Contributor.objects.with_contributions() + + pull_request_ranges_for_achievements = [1, 10, 25, 50, 100] + commit_ranges_for_achievements = [1, 25, 50, 100, 200] + issue_ranges_for_achievements = [1, 5, 10, 25, 50] + comment_ranges_for_achievements = [1, 25, 50, 100, 200] + edition_ranges_for_achievements = [1, 100, 250, 500, 1000] + + def get_context_data(self, **kwargs): + """Add context data for achievement list.""" + self.contributors_amount = Contributor.objects.count() + context = super().get_context_data(**kwargs) + contributors = Contributor.objects.with_contributions() + current_contributor = self._get_cur_contributor() + repositories = self._get_repositories(current_contributor) + contributions = self._aggregate_contributions(repositories) + + context.update(self._calculate_achievements(contributors, contributions)) + + context['current_contributor'] = current_contributor + context['contributors_amount'] = self.contributors_amount + context['contributors_with_any_contribution'] = ( + self._contributors_with_any_contribution(contributors)) + return context + + def _get_cur_contributor(self): + return Contributor.objects.get(login=self.kwargs['slug']) + + def _get_repositories(self, current_contributor): + """Get repositories where the current contributor made contributions.""" + return Repository.objects.select_related( + 'organization', + ).filter( + is_visible=True, + contribution__contributor=current_contributor, + ).annotate( + commits=models.Count('id', filter=models.Q(contribution__type='cit')), + additions=Coalesce(models.Sum('contribution__stats__additions'), 0), + deletions=Coalesce(models.Sum('contribution__stats__deletions'), 0), + pull_requests=models.Count( + 'contribution', filter=models.Q(contribution__type='pr'), + ), + issues=models.Count( + 'contribution', + filter=models.Q(contribution__type='iss')), + comments=models.Count( + 'contribution', + filter=models.Q(contribution__type='cnt')), + ).order_by('organization', 'name') + + def _aggregate_contributions(self, repositories): + """Aggregate all the contributions for the contributor.""" + return repositories.values().aggregate( + contributor_deletions=models.Sum('deletions'), + contributor_additions=models.Sum('additions'), + contributor_commits=models.Sum('commits'), + contributor_pull_requests=models.Sum('pull_requests'), + contributor_issues=models.Sum('issues'), + contributor_comments=models.Sum('comments'), + ) + + def _calculate_achievements(self, contributors, contributions): + """Calculate achievements for various contribution types.""" + finished = [] + unfinished = [] + + context = { + 'commits': contributions['contributor_commits'], + 'pull_requests': contributions['contributor_pull_requests'], + 'issues': contributions['contributor_issues'], + 'comments': contributions['contributor_comments'], + 'total_editions': self._calculate_editions(contributions), + 'total_actions': self._calculate_total_actions(contributions), + 'pull_request_ranges_for_achievements': + self.pull_request_ranges_for_achievements, + } + + # Process each type of achievement + finished, unfinished = self._process_achievements( + finished, unfinished, context, contributors, contributions + ) + context['finished'] = finished + context['unfinished'] = unfinished + context['closed'] = len(finished) + context['all_achievements'] = len(finished) + len(unfinished) + return context + + def _process_achievements( + self, finished, unfinished, context, contributors, contributions + ): + """Process all achievements types (pull request, commit, etc.).""" + # Pull request achievements: + finished, unfinished = self._process_pull_request_achievements( + finished, unfinished, context, contributors, contributions + ) + + # Commit achievements: + finished, unfinished = self._process_commit_achievements( + finished, unfinished, context, contributors, contributions + ) + + # Issue achievements: + finished, unfinished = self._process_issue_achievements( + finished, unfinished, context, contributors, contributions + ) + + # Comment achievements: + finished, unfinished = self._process_comment_achievements( + finished, unfinished, context, contributors, contributions + ) + + # Edition achievements: + finished, unfinished = self._process_edition_achievements( + finished, unfinished, context, contributors, contributions + ) + + return finished, unfinished + + def _process_pull_request_achievements( + self, finished, unfinished, context, contributors, contributions + ): + """Process achievements related to pull requests.""" + for pr_num in self.pull_request_ranges_for_achievements: + context[f'contributors_pull_requests_gte_{pr_num}'] = { + 'stat': contributors.filter(pull_requests__gte=pr_num).count(), + 'acomplished': + self._get_cur_contributor() in contributors.filter( + pull_requests__gte=pr_num + ), + } + a_data = self._create_achievement_data( + pr_num, 'pull_requests', 'Pull requests' + ) + finished, unfinished = self._update_achievement_status( + pr_num, + contributions['contributor_pull_requests'], + a_data, + finished, + unfinished + ) + return finished, unfinished + + def _process_commit_achievements( + self, finished, unfinished, context, contributors, contributions + ): + """Process achievements related to commits.""" + for commit_num in self.commit_ranges_for_achievements: + context[f'contributors_commits_gte_{commit_num}'] = { + 'stat': contributors.filter(commits__gte=commit_num).count(), + 'acomplished': + self._get_cur_contributor() in contributors.filter( + commits__gte=commit_num + ), + } + a_data = self._create_achievement_data( + commit_num, 'commits', 'Commits' + ) + finished, unfinished = self._update_achievement_status( + commit_num, + contributions['contributor_commits'], + a_data, + finished, + unfinished + ) + return finished, unfinished + + def _process_issue_achievements( + self, finished, unfinished, context, contributors, contributions + ): + """Process achievements related to issues.""" + for issue_num in self.issue_ranges_for_achievements: + context[f'contributors_issues_gte_{issue_num}'] = { + 'stat': contributors.filter(issues__gte=issue_num).count(), + 'acomplished': + self._get_cur_contributor() in contributors.filter( + issues__gte=issue_num), + } + a_data = self._create_achievement_data( + issue_num, 'issues', 'Issues' + ) + finished, unfinished = self._update_achievement_status( + issue_num, + contributions['contributor_issues'], + a_data, + finished, + unfinished + ) + return finished, unfinished + + def _process_comment_achievements( + self, finished, unfinished, context, contributors, contributions + ): + """Process achievements related to comments.""" + for comment_num in self.comment_ranges_for_achievements: + context[f'contributors_comments_gte_{comment_num}'] = { + 'stat': contributors.filter(comments__gte=comment_num).count(), + 'acomplished': + self._get_cur_contributor() in contributors.filter( + comments__gte=comment_num), + } + a_data = self._create_achievement_data( + comment_num, 'comments', 'Comments' + ) + finished, unfinished = self._update_achievement_status( + comment_num, + contributions['contributor_comments'], + a_data, + finished, + unfinished + ) + return finished, unfinished + + def _process_edition_achievements( + self, finished, unfinished, context, contributors, contributions + ): + """Process achievements related to code editions (additions + deletions).""" + editions = self._calculate_editions(contributions) + for ed_num in self.edition_ranges_for_achievements: + context[f'contributors_editions_gte_{ed_num}'] = { + 'stat': contributors.filter(editions__gte=ed_num).count(), + 'acomplished': + self._get_cur_contributor() in contributors.filter( + editions__gte=ed_num), + } + a_data = self._create_achievement_data( + ed_num, 'code_editions', 'Additions and deletions' + ) + finished, unfinished = self._update_achievement_status( + ed_num, editions, a_data, finished, unfinished + ) + return finished, unfinished + + def _create_achievement_data(self, num, type_key, type_name): + """Create achievement data dictionary.""" + return { + 'img': f'images/achievments_icons/{type_key}-{num}.svg', + 'name': f'{type_name} (equal to or more than {num})', + 'description': + f"Make {type_name.lower()} in amount of equal to or more than {num}", + } + + def _update_achievement_status(self, num, cur_value, a_data, finished, unfinished): + """Update the achievement status.""" + if num > (0 if cur_value is None else cur_value): + unfinished.append(a_data) + a_data['acomplished'] = False + else: + finished.append(a_data) + return finished, unfinished + + def _calculate_editions(self, contributions): + """Calculate total editions (additions + deletions).""" + return sum( + 0 if edit is None else edit + for edit in [ + contributions['contributor_additions'], + contributions['contributor_deletions'], + ]) + + def _calculate_total_actions(self, contributions): + """Calculate total actions""" + return sum( + 0 if action is None else action + for action in [ + contributions['contributor_commits'], + contributions['contributor_pull_requests'], + contributions['contributor_issues'], + contributions['contributor_comments'], + contributions['contributor_additions'], + contributions['contributor_deletions'], + ]) + + def _contributors_with_any_contribution(self, contributors): + """Get the contributors with any contributions.""" + return { + 'stat': contributors.filter(contribution_amount__gte=1).count(), + 'acomplished': True, + } diff --git a/contributors/views/contributor_compare.py b/contributors/views/contributor_compare.py index be41fc6c..9b92818d 100644 --- a/contributors/views/contributor_compare.py +++ b/contributors/views/contributor_compare.py @@ -9,7 +9,8 @@ class CompareWithYourselfView(ListView): """View of comparing current user with another one.""" model = Contribution - template_name = 'contributors_sections/contributors/contributor_compare_with_yourself.html' # noqa: E501 + template_name = ('contributors_sections/' + 'contributors/contributor_compare_with_yourself.html') slug_field = 'contributor' def get_queryset(self): @@ -54,7 +55,7 @@ def get_context_data(self, **kwargs): ).first().full_name context['me_top_repo'] = me_repo_full_name else: - context['me_top_repo'] = '---' # noqa: E501 + context['me_top_repo'] = '---' enemy_qs = context['filter'].qs.filter( contributor=context['enemy_obj'].pk, @@ -75,5 +76,5 @@ def get_context_data(self, **kwargs): ).first().full_name context['enemy_top_repo'] = enemy_repo_full_name else: - context['enemy_top_repo'] = '---' # noqa: E501 + context['enemy_top_repo'] = '---' return context diff --git a/contributors/views/contributors_views/contributors_for_period.py b/contributors/views/contributors_views/contributors_for_period.py index 0fa4f82c..8ef013ee 100644 --- a/contributors/views/contributors_views/contributors_for_period.py +++ b/contributors/views/contributors_views/contributors_for_period.py @@ -6,7 +6,7 @@ class ListView(contributors.ListView): """A list of contributors with monthly contributions.""" - template_name = 'contributors_sections/contributors/contributors_for_period.html' # noqa: E501 + template_name = 'contributors_sections/contributors/contributors_for_period.html' context_object_name = 'contributors_list' def get_context_data(self, **kwargs): diff --git a/contributors/views/generic_list_views/pull_requests.py b/contributors/views/generic_list_views/pull_requests.py index 304b3dc6..ba4108db 100644 --- a/contributors/views/generic_list_views/pull_requests.py +++ b/contributors/views/generic_list_views/pull_requests.py @@ -23,7 +23,7 @@ class ListView(TableSortSearchAndPaginationMixin, generic.ListView): ) ordering = sortable_fields[0] - template_name = 'contributors_sections/pull_requests/pull_requests_list.html' # noqa: E501 + template_name = 'contributors_sections/pull_requests/pull_requests_list.html' def get_queryset(self): """Get pull requests. diff --git a/contributors/views/organizations_views/organizations.py b/contributors/views/organizations_views/organizations.py index 1b35f34c..a33f5003 100644 --- a/contributors/views/organizations_views/organizations.py +++ b/contributors/views/organizations_views/organizations.py @@ -1,8 +1,6 @@ from django.db.models import Count -from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ from django.views import generic -from django.views.decorators.cache import cache_page from contributors.models import Organization from contributors.views.mixins import TableSortSearchAndPaginationMixin @@ -11,21 +9,13 @@ class ListView(TableSortSearchAndPaginationMixin, generic.ListView): """A list of organizations.""" - @method_decorator(cache_page(60 * 15)) # кэширование на 15 минут - def dispatch(self, *args, **kwargs): - return super().dispatch(*args, **kwargs) - - queryset = ( - Organization.objects.filter( - repository__is_visible=True, - ) - .distinct() - .annotate(repository_count=Count("repository")) - ) - template_name = "contributors_sections/organizations/organizations_list.html" # noqa: E501 + queryset = Organization.objects.filter( + repository__is_visible=True, + ).distinct().annotate(repository_count=Count('repository')) + template_name = 'contributors_sections/organizations/organizations_list.html' # noqa: E501 sortable_fields = ( - "name", - ("repository_count", _("Repositories")), + 'name', + ('repository_count', _("Repositories")), ) - searchable_fields = ("name",) + searchable_fields = ('name',) ordering = sortable_fields[0] diff --git a/contributors/views/repositories_views/repository.py b/contributors/views/repositories_views/repository.py index ec198a20..8eff112f 100644 --- a/contributors/views/repositories_views/repository.py +++ b/contributors/views/repositories_views/repository.py @@ -7,7 +7,7 @@ class RepoContributorList(contributors.ListView): """A repository's details.""" - template_name = 'contributors_sections/repositories/repository_details.html' # noqa: E501 + template_name = 'contributors_sections/repositories/repository_details.html' def get_queryset(self): """Get a dataset.""" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..898dda7a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,86 @@ +# All configuration for plugins and other utils is defined here. +# Read more about `setup.cfg`: +# https://docs.python.org/3/distutils/configfile.html + +[flake8] +# Base flake8 configuration: +# https://flake8.pycqa.org/en/latest/user/configuration.html +statistics = False +doctests = True +enable-extensions = G +count = True +max-string-usages = 4 +max-local-variables = 10 +max-line-length = 79 + +# Plugins: +accept-encodings = utf-8 +max-complexity = 6 +radon-max-cc = 10 +radon-no-assert = True +radon-show-closures = True + +# Exclude some directories: +exclude = + .git + __pycache__ + migrations + .venv + +# Ignore some checks for Django's standard files: +per-file-ignores = + config/*.py manage.py: + # Possible binding to all interfaces. + S104, + + contrib_extras.py: + # No specific ignore rules needed + + __init__.py: + # imported but unused + F401 + + github_lib.py, github_webhook.py, misc.py: + # No specific ignore rules needed + + fetchdata.py: + # No specific ignore rules needed + + */tests/*.py: + # Missing docstring in public method + D102 + + contributors/views/contributor.py: + # No specific ignore rules needed + + contributors/views/contributor_compare.py: + # No specific ignore rules needed + +ignore = + # Coding magic comment not found + C101, + # Missing parameter(s) in Docstring + DAR101, + # Missing "Returns" in Docstring + DAR201, + # Missing "Yields" in Docstring + DAR301, + # Missing exception(s) in Raises section + DAR401, + # Missing docstring in public module + D100, + # Missing docstring in public package + D104, + # Missing docstring in public nested class + D106, + # Remove bad quotes + Q000, + # Line break before binary operator + W503 + + +[isort] +multi_line_output = 3 +include_trailing_comma = true +# Should be: max-line-length - 1 +line_length = 78 diff --git a/static/css/base.css b/static/css/base.css index 8f0045b5..b1fb3912 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -200,3 +200,11 @@ a:not([class]) { a:not([class]):hover { text-decoration: underline; } + +.x-vw { + width: 50vw; +} + +.x-h { + height: max-content; +} \ No newline at end of file diff --git a/static/images/achievments_icons/code_edition-1.svg b/static/images/achievments_icons/code_editions-1.svg similarity index 100% rename from static/images/achievments_icons/code_edition-1.svg rename to static/images/achievments_icons/code_editions-1.svg diff --git a/static/images/achievments_icons/code_editioins-1000.svg b/static/images/achievments_icons/code_editions-1000.svg similarity index 100% rename from static/images/achievments_icons/code_editioins-1000.svg rename to static/images/achievments_icons/code_editions-1000.svg diff --git a/static/images/achievments_icons/comment-1.svg b/static/images/achievments_icons/comments-1.svg similarity index 100% rename from static/images/achievments_icons/comment-1.svg rename to static/images/achievments_icons/comments-1.svg diff --git a/static/images/achievments_icons/commit-1.svg b/static/images/achievments_icons/commits-1.svg similarity index 100% rename from static/images/achievments_icons/commit-1.svg rename to static/images/achievments_icons/commits-1.svg diff --git a/static/images/achievments_icons/done.svg b/static/images/achievments_icons/done.svg new file mode 100644 index 00000000..2a663ed1 --- /dev/null +++ b/static/images/achievments_icons/done.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/static/images/achievments_icons/issue-1.svg b/static/images/achievments_icons/issues-1.svg similarity index 100% rename from static/images/achievments_icons/issue-1.svg rename to static/images/achievments_icons/issues-1.svg diff --git a/static/images/achievments_icons/pull_request-1.svg b/static/images/achievments_icons/pull_requests-1.svg similarity index 100% rename from static/images/achievments_icons/pull_request-1.svg rename to static/images/achievments_icons/pull_requests-1.svg diff --git a/templates/components/footer.html b/templates/components/footer.html index 4411933a..767a72c8 100644 --- a/templates/components/footer.html +++ b/templates/components/footer.html @@ -7,7 +7,22 @@
-

{% trans "Other projects" %}

+
- - diff --git a/templates/components/tables/achievement_percentage_field.html b/templates/components/tables/achievement_percentage_field.html index 89f3e508..c479e2e1 100644 --- a/templates/components/tables/achievement_percentage_field.html +++ b/templates/components/tables/achievement_percentage_field.html @@ -1,8 +1,8 @@ {% load i18n static mathfilters %} -
+
-
+
{% trans achievement_name %}
{% trans achievement_description %}
diff --git a/templates/components/tables/achievements_list.html b/templates/components/tables/achievements_list.html index 40d6e128..cd618929 100644 --- a/templates/components/tables/achievements_list.html +++ b/templates/components/tables/achievements_list.html @@ -2,6 +2,10 @@ {% load i18n static mathfilters %} {% block tbody %} + +
+ {% trans "Total achievements" %} {{ closed }} / {{all_achievements}} +
- {% with achievement_name="Hexlet friend" achievement_description="Make any contribution to Hexlet projects" achievement_made_count=contributors_with_any_contribution %} + {% with achievement_name="Hexlet friend" achievement_description="Make any contribution to Hexlet projects" achievement_made_count=contributors_with_any_contribution.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_with_any_contribution.acomplished %} + + {% endif %} + - {% trans 'Pull requests (equal to or more than 1)' %} - {% with achievement_name="Pull requests (equal to or more than 1)" achievement_description="Make pull requests in amount of equal to or more than 1" achievement_made_count=contributors_pull_requests_gte_1 %} + {% with achievement_name="Pull requests (equal to or more than 1)" achievement_description="Make pull requests in amount of equal to or more than 1" achievement_made_count=contributors_pull_requests_gte_1.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_pull_requests_gte_1.acomplished %} + + {% endif %} + @@ -43,10 +59,16 @@ width="50"> - {% with achievement_name="Pull requests (equal to or more than 10)" achievement_description="Make pull requests in amount of equal to or more than 10" achievement_made_count=contributors_pull_requests_gte_10 %} + {% with achievement_name="Pull requests (equal to or more than 10)" achievement_description="Make pull requests in amount of equal to or more than 10" achievement_made_count=contributors_pull_requests_gte_10.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_pull_requests_gte_10.acomplished %} + + {% endif %} + @@ -57,10 +79,16 @@ width="50"> - {% with achievement_name="Pull requests (equal to or more than 25)" achievement_description="Make pull requests in amount of equal to or more than 25" achievement_made_count=contributors_pull_requests_gte_25 %} + {% with achievement_name="Pull requests (equal to or more than 25)" achievement_description="Make pull requests in amount of equal to or more than 25" achievement_made_count=contributors_pull_requests_gte_25.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_pull_requests_gte_25.acomplished %} + + {% endif %} + @@ -71,10 +99,16 @@ width="50"> - {% with achievement_name="Pull requests (equal to or more than 50)" achievement_description="Make pull requests in amount of equal to or more than 50" achievement_made_count=contributors_pull_requests_gte_50 %} + {% with achievement_name="Pull requests (equal to or more than 50)" achievement_description="Make pull requests in amount of equal to or more than 50" achievement_made_count=contributors_pull_requests_gte_50.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_pull_requests_gte_50.acomplished %} + + {% endif %} + @@ -85,26 +119,38 @@ width="50"> - {% with achievement_name="Pull requests (equal to or more than 100)" achievement_description="Make pull requests in amount of equal to or more than 100" achievement_made_count=contributors_pull_requests_gte_100 %} + {% with achievement_name="Pull requests (equal to or more than 100)" achievement_description="Make pull requests in amount of equal to or more than 100" achievement_made_count=contributors_pull_requests_gte_100.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_pull_requests_gte_10.acomplished %} + + {% endif %} + - {% trans 'Commits (equal to or more than 1)' %} - {% with achievement_name="Commits (equal to or more than 1)" achievement_description="Make commits in amount of equal to or more than 1" achievement_made_count=contributors_commits_gte_1 %} + {% with achievement_name="Commits (equal to or more than 1)" achievement_description="Make commits in amount of equal to or more than 1" achievement_made_count=contributors_commits_gte_1.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_commits_gte_1.acomplished %} + + {% endif %} + @@ -115,10 +161,16 @@ width="50"> - {% with achievement_name="Commits (equal to or more than 25)" achievement_description="Make commits in amount of equal to or more than 25" achievement_made_count=contributors_commits_gte_25 %} + {% with achievement_name="Commits (equal to or more than 25)" achievement_description="Make commits in amount of equal to or more than 25" achievement_made_count=contributors_commits_gte_25.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_commits_gte_25.acomplished %} + + {% endif %} + @@ -129,10 +181,16 @@ width="50"> - {% with achievement_name="Commits (equal to or more than 50)" achievement_description="Make commits in amount of equal to or more than 50" achievement_made_count=contributors_commits_gte_50 %} + {% with achievement_name="Commits (equal to or more than 50)" achievement_description="Make commits in amount of equal to or more than 50" achievement_made_count=contributors_commits_gte_50.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_commits_gte_50.acomplished %} + + {% endif %} + @@ -143,10 +201,16 @@ width="50"> - {% with achievement_name="Commits (equal to or more than 100)" achievement_description="Make commits in amount of equal to or more than 100" achievement_made_count=contributors_commits_gte_100 %} + {% with achievement_name="Commits (equal to or more than 100)" achievement_description="Make commits in amount of equal to or more than 100" achievement_made_count=contributors_commits_gte_100.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_commits_gte_100.acomplished %} + + {% endif %} + @@ -157,26 +221,38 @@ width="50"> - {% with achievement_name="Commits (equal to or more than 200)" achievement_description="Make commits in amount of equal to or more than 200" achievement_made_count=contributors_commits_gte_200 %} + {% with achievement_name="Commits (equal to or more than 200)" achievement_description="Make commits in amount of equal to or more than 200" achievement_made_count=contributors_commits_gte_200.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_commits_gte_200.acomplished %} + + {% endif %} + - {% trans 'Issues (equal to or more than 1)' %} - {% with achievement_name="Issues (equal to or more than 1)" achievement_description="Make issues in amount of equal to or more than 1" achievement_made_count=contributors_issues_gte_1 %} + {% with achievement_name="Issues (equal to or more than 1)" achievement_description="Make issues in amount of equal to or more than 1" achievement_made_count=contributors_issues_gte_1.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_issues_gte_1.acomplished %} + + {% endif %} + @@ -187,10 +263,16 @@ width="50"> - {% with achievement_name="Issues (equal to or more than 5)" achievement_description="Make issues in amount of equal to or more than 5" achievement_made_count=contributors_issues_gte_5 %} + {% with achievement_name="Issues (equal to or more than 5)" achievement_description="Make issues in amount of equal to or more than 5" achievement_made_count=contributors_issues_gte_5.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_issues_gte_5.acomplished %} + + {% endif %} + @@ -201,10 +283,16 @@ width="50"> - {% with achievement_name="Issues (equal to or more than 10)" achievement_description="Make issues in amount of equal to or more than 10" achievement_made_count=contributors_issues_gte_10 %} + {% with achievement_name="Issues (equal to or more than 10)" achievement_description="Make issues in amount of equal to or more than 10" achievement_made_count=contributors_issues_gte_10.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_issues_gte_10.acomplished %} + + {% endif %} + @@ -215,10 +303,16 @@ width="50"> - {% with achievement_name="Issues (equal to or more than 25)" achievement_description="Make issues in amount of equal to or more than 25" achievement_made_count=contributors_issues_gte_25 %} + {% with achievement_name="Issues (equal to or more than 25)" achievement_description="Make issues in amount of equal to or more than 25" achievement_made_count=contributors_issues_gte_25.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_issues_gte_25.acomplished %} + + {% endif %} + @@ -229,26 +323,38 @@ width="50"> - {% with achievement_name="Issues (equal to or more than 50)" achievement_description="Make issues in amount of equal to or more than 50" achievement_made_count=contributors_issues_gte_50 %} + {% with achievement_name="Issues (equal to or more than 50)" achievement_description="Make issues in amount of equal to or more than 50" achievement_made_count=contributors_issues_gte_50.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_issues_gte_50.acomplished %} + + {% endif %} + - {% trans 'Comments (equal to or more than 1)' %} - {% with achievement_name="Comments (equal to or more than 1)" achievement_description="Make comments in amount of equal to or more than 1" achievement_made_count=contributors_comments_gte_1 %} + {% with achievement_name="Comments (equal to or more than 1)" achievement_description="Make comments in amount of equal to or more than 1" achievement_made_count=contributors_comments_gte_1.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_comments_gte_1.acomplished %} + + {% endif %} + @@ -259,10 +365,16 @@ width="50"> - {% with achievement_name="Comments (equal to or more than 25)" achievement_description="Make comments in amount of equal to or more than 25" achievement_made_count=contributors_comments_gte_25 %} + {% with achievement_name="Comments (equal to or more than 25)" achievement_description="Make comments in amount of equal to or more than 25" achievement_made_count=contributors_comments_gte_25.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_comments_gte_25.acomplished %} + + {% endif %} + @@ -273,10 +385,16 @@ width="50"> - {% with achievement_name="Comments (equal to or more than 50)" achievement_description="Make comments in amount of equal to or more than 50" achievement_made_count=contributors_comments_gte_50 %} + {% with achievement_name="Comments (equal to or more than 50)" achievement_description="Make comments in amount of equal to or more than 50" achievement_made_count=contributors_comments_gte_50.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_comments_gte_50.acomplished %} + + {% endif %} + @@ -287,10 +405,16 @@ width="50"> - {% with achievement_name="Comments (equal to or more than 100)" achievement_description="Make comments in amount of equal to or more than 100" achievement_made_count=contributors_comments_gte_100 %} + {% with achievement_name="Comments (equal to or more than 100)" achievement_description="Make comments in amount of equal to or more than 100" achievement_made_count=contributors_comments_gte_100.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_comments_gte_100.acomplished %} + + {% endif %} + @@ -301,25 +425,37 @@ width="50"> - {% with achievement_name="Comments (equal to or more than 200)" achievement_description="Make comments in amount of equal to or more than 200" achievement_made_count=contributors_comments_gte_200 %} + {% with achievement_name="Comments (equal to or more than 200)" achievement_description="Make comments in amount of equal to or more than 200" achievement_made_count=contributors_comments_gte_200.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_comments_gte_200.acomplished %} + + {% endif %} + - {% trans 'Additions and deletions (equal to or more than 1)' %} - {% with achievement_name="Additions and deletions (equal to or more than 1)" achievement_description="Make additions and deletions in amount of equal to or more than 1" achievement_made_count=contributors_editions_gte_1 %} + {% with achievement_name="Additions and deletions (equal to or more than 1)" achievement_description="Make additions and deletions in amount of equal to or more than 1" achievement_made_count=contributors_editions_gte_1.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_editions_gte_1.acomplished %} + + {% endif %} + @@ -330,10 +466,16 @@ width="50"> - {% with achievement_name="Additions and deletions (equal to or more than 100)" achievement_description="Make additions and deletions in amount of equal to or more than 100" achievement_made_count=contributors_editions_gte_100 %} + {% with achievement_name="Additions and deletions (equal to or more than 100)" achievement_description="Make additions and deletions in amount of equal to or more than 100" achievement_made_count=contributors_editions_gte_100.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_editions_gte_100.acomplished %} + + {% endif %} + @@ -344,10 +486,16 @@ width="50"> - {% with achievement_name="Additions and deletions (equal to or more than 250)" achievement_description="Make additions and deletions in amount of equal to or more than 250" achievement_made_count=contributors_editions_gte_250 %} + {% with achievement_name="Additions and deletions (equal to or more than 250)" achievement_description="Make additions and deletions in amount of equal to or more than 250" achievement_made_count=contributors_editions_gte_250.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_editions_gte_250.acomplished %} + + {% endif %} + @@ -358,23 +506,37 @@ width="50"> - {% with achievement_name="Additions and deletions (equal to or more than 500)" achievement_description="Make additions and deletions in amount of equal to or more than 500" achievement_made_count=contributors_editions_gte_500 %} + {% with achievement_name="Additions and deletions (equal to or more than 500)" achievement_description="Make additions and deletions in amount of equal to or more than 500" achievement_made_count=contributors_editions_gte_500.stat%} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_editions_gte_500.acomplished %} + + {% endif %} + - {% trans 'Additions and deletions (equal to or more than 1000)' %} - {% with achievement_name="Additions and deletions (equal to or more than 1000)" achievement_description="Make additions and deletions in amount of equal to or more than 1000" achievement_made_count=contributors_editions_gte_1000 %} + {% with achievement_name="Additions and deletions (equal to or more than 1000)" achievement_description="Make additions and deletions in amount of equal to or more than 1000" achievement_made_count=contributors_editions_gte_1000.stat %} {% include './achievement_percentage_field.html' %} {% endwith %} + + {% if contributors_editions_gte_1000.acomplished %} + + {% endif %} + + + {% endblock %} diff --git a/templates/components/tables/contributor_achievement_percentage_field.html b/templates/components/tables/contributor_achievement_percentage_field.html new file mode 100644 index 00000000..b4038086 --- /dev/null +++ b/templates/components/tables/contributor_achievement_percentage_field.html @@ -0,0 +1,15 @@ +{% load i18n static mathfilters %} +{% load i18n static contrib_extras %} + +
+
+
+
{% trans achievement_name %}
+
{% trans achievement_description %}
+
+
+ +
+ {% calc_percent_achievement contribution achievement_made_count %}% +
+
diff --git a/templates/components/tables/contributor_achievements_list.html b/templates/components/tables/contributor_achievements_list.html new file mode 100644 index 00000000..ba2f51ae --- /dev/null +++ b/templates/components/tables/contributor_achievements_list.html @@ -0,0 +1,45 @@ +{% extends './list_as_table.html' %} +{% load i18n static mathfilters %} + +{% block tbody %} + +{% for item in finished %} + + + + + +
+
+
{{item.name}}
+
{{item.description}}
+
+
+ + +{% endfor %} + +
+ +{% for item in unfinished %} + + + + + + +
+
+
{{item.name}}
+
{{item.description}}
+
+
+ + +{% endfor %} + +{% endblock %} \ No newline at end of file diff --git a/templates/contributor/contributor_achievements_list.html b/templates/contributor/contributor_achievements_list.html new file mode 100644 index 00000000..213a42aa --- /dev/null +++ b/templates/contributor/contributor_achievements_list.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% load i18n static %} + +{% block content %} + +

{% trans "Achievements" %}

+ + + + +{% endblock %} diff --git a/templates/contributor/contributor_details.html b/templates/contributor/contributor_details.html index 96bae965..c0d6f467 100644 --- a/templates/contributor/contributor_details.html +++ b/templates/contributor/contributor_details.html @@ -19,7 +19,7 @@

{{ contributor.login }}

GitHub

- + {% trans 'Achievements' %}