From 7bab58ac813d2087ccea852d61720ddba66a77f2 Mon Sep 17 00:00:00 2001 From: Facundo Batista Date: Tue, 21 Nov 2023 11:41:29 -0300 Subject: [PATCH] Removed the old jobs code. (#583) --- community/tests/test_homepage.py | 11 +- community/views.py | 16 +- events/models.py | 12 +- joboffers/migrations/0016_migrate_old_jobs.py | 87 ----------- .../migrations/0017_migrate_old_jobs_tags.py | 32 ---- .../0018_migrate_old_job_comments.py | 32 ---- joboffers/tests/factories.py | 2 +- jobs/__init__.py | 0 jobs/admin.py | 6 - jobs/forms.py | 117 --------------- jobs/migrations/0001_initial.py | 57 ------- jobs/migrations/0002_auto_20170326_1616.py | 20 --- .../0003_normalize_tags_20180428_1955.py | 43 ------ jobs/migrations/__init__.py | 0 jobs/models.py | 107 -------------- jobs/tests/__init__.py | 0 jobs/tests/factories.py | 26 ---- jobs/tests/test_utils.py | 22 --- jobs/tests/test_views.py | 139 ------------------ jobs/urls.py | 18 --- jobs/utils.py | 15 -- jobs/views.py | 137 ----------------- pyarweb/settings/base.py | 1 - pyarweb/urls.py | 1 - 24 files changed, 25 insertions(+), 876 deletions(-) delete mode 100644 joboffers/migrations/0016_migrate_old_jobs.py delete mode 100644 joboffers/migrations/0017_migrate_old_jobs_tags.py delete mode 100644 joboffers/migrations/0018_migrate_old_job_comments.py delete mode 100644 jobs/__init__.py delete mode 100644 jobs/admin.py delete mode 100644 jobs/forms.py delete mode 100644 jobs/migrations/0001_initial.py delete mode 100644 jobs/migrations/0002_auto_20170326_1616.py delete mode 100644 jobs/migrations/0003_normalize_tags_20180428_1955.py delete mode 100644 jobs/migrations/__init__.py delete mode 100644 jobs/models.py delete mode 100644 jobs/tests/__init__.py delete mode 100644 jobs/tests/factories.py delete mode 100644 jobs/tests/test_utils.py delete mode 100644 jobs/tests/test_views.py delete mode 100644 jobs/urls.py delete mode 100644 jobs/utils.py delete mode 100644 jobs/views.py diff --git a/community/tests/test_homepage.py b/community/tests/test_homepage.py index b43337c7..3b8d25e5 100644 --- a/community/tests/test_homepage.py +++ b/community/tests/test_homepage.py @@ -6,7 +6,8 @@ from community.views import HomePageView, RECENT_ITEMS_LEN from events.tests.factories import EventFactory -from jobs.tests.factories import JobFactory +from joboffers.models import OfferState +from joboffers.tests.factories import JobOfferFactory from news.tests.factories import NewsArticleFactory @@ -32,11 +33,11 @@ def test_included_events_have_correct_fields(self): # Events already have a description field def test_jobs_are_included_in_recent(self): - job = JobFactory() + job = JobOfferFactory(state=OfferState.ACTIVE) self.assertEqual([job], self.get_context_data()['recent']) def test_included_jobs_have_correct_fields(self): - JobFactory() + JobOfferFactory(state=OfferState.ACTIVE) job = self.get_context_data()['recent'][0] self.assertEqual(job.category, 'Trabajos') # jobs already have 'created', 'title' and 'description' fields @@ -55,7 +56,7 @@ def test_included_news_have_correct_fields(self): # Independent of the Model type, all list items are sorted by their 'created' field def test_items_are_sorted_by_the_created_field(self): - job = JobFactory(set_created='1985-10-26 09:00Z') # Middle-age ;-) + job = JobOfferFactory(state=OfferState.ACTIVE, set_created='1985-10-26 09:00Z') # Middle event = EventFactory(start_at='1955-11-12 06:38Z') # Oldest article = NewsArticleFactory(set_created='2015-10-21 09:00Z') # Most recent # Assert the models are in chronological order @@ -64,7 +65,7 @@ def test_items_are_sorted_by_the_created_field(self): def test_recent_is_a_list_with_at_most_10_items(self): # Create more than RECENT_ITEMS_LEN models and assert that only 10 are kept as recent for i in range(RECENT_ITEMS_LEN): - JobFactory() + JobOfferFactory(state=OfferState.ACTIVE) EventFactory() NewsArticleFactory() # The loop above creates RECENT_ITEMS_LEN * 3 items diff --git a/community/views.py b/community/views.py index dbc6046c..ca76e64c 100644 --- a/community/views.py +++ b/community/views.py @@ -10,7 +10,7 @@ from django.views.generic.list import MultipleObjectMixin from django.utils.timezone import now from events.models import Event -from jobs.models import Job +from joboffers.models import JobOffer, OfferState from news.models import NewsArticle RECENT_ITEMS_LEN = 10 @@ -26,9 +26,8 @@ class HomePageView(TemplateView): template_name = "community/index.html" def get_events_for_context(self): - """Ensure events have 'category', created' and 'title' fields""" + """Get events annotated for proper usage.""" events = Event.objects.filter(start_at__lt=now()) - events = events.order_by('-start_at') events = events.annotate( created=F('start_at'), title=F('name'), @@ -36,13 +35,16 @@ def get_events_for_context(self): return aux_qs_to_list_for_context(events) def get_jobs_for_context(self): - """Ensure jobs have a 'category' field""" - jobs = Job.objects.order_by('-created') - jobs = jobs.annotate(category=Value('Trabajos', output_field=CharField())) + """Get jobs annotated for proper usage.""" + jobs = JobOffer.objects.filter(state=OfferState.ACTIVE) + jobs = jobs.annotate( + category=Value('Trabajos', output_field=CharField()), + created=F('created_at'), + ) return aux_qs_to_list_for_context(jobs) def get_news_for_context(self): - """Ensure news have 'category' and 'description' fields""" + """Get news annotated for proper usage.""" news = NewsArticle.objects.order_by('-created') news = news.annotate( description=F('body'), diff --git a/events/models.py b/events/models.py index d9b3c638..0c108006 100644 --- a/events/models.py +++ b/events/models.py @@ -5,8 +5,6 @@ from autoslug import AutoSlugField -from jobs.models import JOB_SENIORITIES - GENDER_OPTIONS = ( ('female', _('femenino')), @@ -14,6 +12,14 @@ ('Otro', _('otro')), ) +JOB_SENIORITIES = ( + ('Trainee', 'Trainee'), + ('Junior', 'Junior'), + ('Semi Senior', 'Semi Senior'), + ('Senior', 'Senior'), + ('Guido', 'Soy Guido Van Rossum'), +) + class Event(models.Model): """A PyAr events.""" @@ -55,7 +61,7 @@ class EventParticipation(models.Model): max_length=100, blank=True, default='', - choices=JOB_SENIORITIES + (('guido', _('Soy Guido Van Rossum')),), + choices=JOB_SENIORITIES, verbose_name=_('experiencia') ) diff --git a/joboffers/migrations/0016_migrate_old_jobs.py b/joboffers/migrations/0016_migrate_old_jobs.py deleted file mode 100644 index 1892e484..00000000 --- a/joboffers/migrations/0016_migrate_old_jobs.py +++ /dev/null @@ -1,87 +0,0 @@ -# Generated by Django 3.2.12 on 2022-06-13 17:30 -import html -import re - -from datetime import timedelta - -from django.db import migrations -from django.utils.timezone import now - - -def get_short_description(description): - """ - Deduce the short_description from a given html description string - """ - description_stripped_tags = re.sub(r'<[^>]*>', ' ', description) - description_without_spaces = re.sub(r'\s+', ' ', - description_stripped_tags).strip() - description_unescaped = html.unescape(description_without_spaces) - return description_unescaped[:512] - - -def forward(apps, schema_editor): - Job = apps.get_model('jobs', 'Job') - JobOffer = apps.get_model('joboffers', 'JobOffer') - UserCompanyProfile = apps.get_model('pycompanies', 'UserCompanyProfile') - User = apps.get_model('auth', 'User') - - superuser = User.objects.filter(is_superuser=True).first() - - for job in Job.objects.all(): - if not job.email: - email = UserCompanyProfile.objects.filter( - company=job.company).first().user.email - else: - email = job.email - - SENIORITY_TO_EXPERIENCE_MAP = { - '': '0', # Tomo por defecto 0 a falta de otro valor mejor - 'Trainee': '0', - 'Junior': '2+', - 'Semi Senior': '5+', - 'Senior': '10+' - } - - experience = SENIORITY_TO_EXPERIENCE_MAP[job.seniority] - remoteness = 'REMOTE' if job.remote_work else 'IN_OFFICE' - - if job.is_active: - thirty_days_ago = now() - timedelta(days=30) - - if job.modified < thirty_days_ago: - state = 'EXPIRED' - else: - state = 'ACTIVE' - elif job.jobinactivated_set.exists(): - state = 'REJECTED' - else: - state = 'DEACTIVATED' - - joboffer = JobOffer(id=job.id, - title=job.title, - company=job.company, - location=job.location, - contact_mail=email, - experience=experience, - remoteness=remoteness, - hiring_type='OTHER', - description=job.description, - created_at=job.created, - created_by=job.owner, - modified_at=job.modified, - modified_by=superuser, - short_description=get_short_description( - job.description), - state=state) - - joboffer.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('joboffers', '0015_alter_joboffer_company'), - ('jobs', '0003_normalize_tags_20180428_1955'), - ] - - operations = [migrations.RunPython(forward, migrations.RunPython.noop)] diff --git a/joboffers/migrations/0017_migrate_old_jobs_tags.py b/joboffers/migrations/0017_migrate_old_jobs_tags.py deleted file mode 100644 index f48a3119..00000000 --- a/joboffers/migrations/0017_migrate_old_jobs_tags.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.2.12 on 2022-06-13 17:30 - -from django.db import migrations -from joboffers.models import JobOffer -from jobs.models import Job - - -def forward(apps, schema_editor): - ContentType = apps.get_model('contenttypes', 'ContentType') - TaggedItem = apps.get_model('taggit', 'TaggedItem') - - job_content_type = ContentType.objects.get_for_model(Job) - joboffer_content_type = ContentType.objects.get_for_model(JobOffer) - - tagged_items = TaggedItem.objects.filter(content_type=job_content_type) - - new_tags = [ - TaggedItem(content_type=joboffer_content_type, - object_id=tagged_item.object_id, - tag=tagged_item.tag) for tagged_item in tagged_items - ] - for new_tag in new_tags: - new_tag.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('joboffers', '0016_migrate_old_jobs'), - ] - - operations = [migrations.RunPython(forward, migrations.RunPython.noop)] diff --git a/joboffers/migrations/0018_migrate_old_job_comments.py b/joboffers/migrations/0018_migrate_old_job_comments.py deleted file mode 100644 index 8482ed59..00000000 --- a/joboffers/migrations/0018_migrate_old_job_comments.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.2.12 on 2022-06-13 17:30 - -from django.db import migrations - - -def forward(apps, schema_editor): - JobInactivated = apps.get_model('jobs', 'jobinactivated') - JobOfferComment = apps.get_model('joboffers', 'joboffercomment') - - User = apps.get_model('auth', 'User') - - superuser = User.objects.filter(is_superuser=True).first() - - for old_comment in JobInactivated.objects.filter( - job__company__isnull=False): - new_comment = JobOfferComment( - text=f"{old_comment.reason}: {old_comment.comment}", - comment_type='MODERATION', - created_at=old_comment.created, - created_by=superuser, - joboffer_id=old_comment.job_id) - - new_comment.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('joboffers', '0017_migrate_old_jobs_tags'), - ] - - operations = [migrations.RunPython(forward, migrations.RunPython.noop)] diff --git a/joboffers/tests/factories.py b/joboffers/tests/factories.py index 2ea17a07..65f35af4 100644 --- a/joboffers/tests/factories.py +++ b/joboffers/tests/factories.py @@ -39,7 +39,7 @@ def set_created(obj, create, extracted, **kwargs): """ if extracted: - obj.created = extracted + obj.created_at = extracted obj.save() @post_generation diff --git a/jobs/__init__.py b/jobs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/jobs/admin.py b/jobs/admin.py deleted file mode 100644 index 6ca0741b..00000000 --- a/jobs/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from .models import Job, JobInactivated - - -admin.site.register(Job) -admin.site.register(JobInactivated) diff --git a/jobs/forms.py b/jobs/forms.py deleted file mode 100644 index 36f08588..00000000 --- a/jobs/forms.py +++ /dev/null @@ -1,117 +0,0 @@ -from random import choice -from django import forms -from django.conf import settings -from django.utils.translation import gettext_lazy as _ -from django.urls import reverse -from .models import Job, JobInactivated -from crispy_forms.layout import Submit, Reset, Layout -from crispy_forms.helper import FormHelper -from django_summernote.widgets import SummernoteInplaceWidget -from sanitizer.forms import SanitizedCharField - -from jobs import utils - - -class JobForm(forms.ModelForm): - """A PyAr Jobs form.""" - - description = SanitizedCharField( - allowed_tags=settings.ALLOWED_HTML_TAGS_INPUT, - allowed_attributes=settings.ALLOWED_HTML_ATTRIBUTES_INPUT, - strip=False, widget=SummernoteInplaceWidget()) - - def __init__(self, *args, **kwargs): - super(JobForm, self).__init__(*args, **kwargs) - - company_help_text = ''.join(( - 'Opcionalmente elija la empresa que ofrece el puesto. ', - 'Si la empresa no aparece en el listado, puede registrarla ', - 'haciendo click {0}'.format( - 'aquí' - ).format( - reverse('companies:add') - ))) - title_help_text = ''.join(( - 'Título del empleo, por ejemplo: {0}'.format( - choice([ - 'Backend Developer', - 'Django Developer', - 'Frontend Developer', - 'Senior Django Developer', - 'Full Stack Python Developer', - ]) - ) - )) - tags_help_text = ''.join(( - 'Agregue algunos tags / etiquetas ', - 'que esten relacionadas con el puesto de trabajo. ' - 'Los tags deben estar separados por comas, por ejemplo: ', - 'Django, Python, MySQL, Linux' - )) - - seniority_help_text = ''.join(( - 'Opcionalmente puede especificar la experiencia requerida ', - 'para el puesto.' - )) - - remote_work_help_text = ''.join(( - 'Se permite la modalidad de trabajo desde casa (homeworking).' - )) - - self.fields['title'].label = 'Título de la oferta' - self.fields['tags'].label = 'Etiquetas / Tags / Tecnologías' - self.fields['tags'].required = False - self.fields['title'].help_text = title_help_text - self.fields['tags'].help_text = tags_help_text - self.fields['seniority'].help_text = seniority_help_text - self.fields['company'].help_text = company_help_text - self.fields['remote_work'].help_text = remote_work_help_text - self.fields['description'].label = 'Descripción' - self.helper = FormHelper() - self.helper.layout = Layout( - 'title', - 'company', - 'location', - 'email', - 'seniority', - 'remote_work', - 'tags', - 'description', - ) - self.helper.add_input(Submit('job_submit', _('Guardar'))) - self.helper.add_input(Reset('job_reset', _('Limpiar'), - css_class='btn-default')) - - def clean_tags(self): - tags = self.cleaned_data.get('tags') - self.cleaned_data['tags'] = utils.normalize_tags(tags) - return self.cleaned_data['tags'] - - class Meta: - model = Job - exclude = ('owner', 'is_active') - - -class JobInactivateForm(forms.ModelForm): - """ Form to inactivate Job """ - - send_email = forms.BooleanField(label='¿Enviar mail al dueño del aviso?', - required=False) - - def __init__(self, *args, **kwargs): - super(JobInactivateForm, self).__init__(*args, **kwargs) - - self.helper = FormHelper() - self.helper.layout = Layout( - 'reason', - 'comment', - 'send_email', - ) - - self.helper.add_input(Submit('job_inactivate_submit', _('Guardar'))) - self.helper.add_input(Reset('job_inactivate_reset', _('Limpiar'), - css_class='btn-default')) - - class Meta: - model = JobInactivated - exclude = ('job', ) diff --git a/jobs/migrations/0001_initial.py b/jobs/migrations/0001_initial.py deleted file mode 100644 index 1b3fb2b1..00000000 --- a/jobs/migrations/0001_initial.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -import autoslug.fields -import django.utils.timezone -import model_utils.fields -import taggit_autosuggest.managers -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('taggit', '0002_auto_20150616_2121'), - ('pycompanies', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Job', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), - ('title', models.CharField(max_length=255, verbose_name='Título')), - ('description', models.TextField(verbose_name='Descripción')), - ('location', models.CharField(max_length=100, verbose_name='Lugar')), - ('email', models.EmailField(max_length=254)), - ('created', models.DateTimeField(auto_now_add=True)), - ('modified', models.DateTimeField(auto_now=True)), - ('remote_work', models.BooleanField(default=False, verbose_name='Trabajo Remoto')), - ('seniority', models.CharField(max_length=100, default='', blank=True, verbose_name='Experiencia', choices=[('Trainee', 'Trainee'), ('Junior', 'Junior'), ('Semi Senior', 'Semi Senior'), ('Senior', 'Senior')])), - ('slug', autoslug.fields.AutoSlugField(editable=False, unique=True)), - ('is_active', models.BooleanField(default=True)), - ('company', models.ForeignKey(null=True, to='pycompanies.Company', blank=True, verbose_name='Empresa', on_delete=models.CASCADE)), - ('owner', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), - ('tags', taggit_autosuggest.managers.TaggableManager(through='taggit.TaggedItem', to='taggit.Tag', help_text='A comma-separated list of tags.', verbose_name='Etiquetas')), - ], - options={ - 'ordering': ['-created'], - }, - ), - migrations.CreateModel( - name='JobInactivated', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), - ('reason', models.CharField(max_length=100, verbose_name='Motivo', choices=[('No es un trabajo relacionado con Python', 'No es un trabajo relacionado con Python'), ('Spam', 'Spam'), ('Información insuficiente', 'Información insuficiente')])), - ('comment', models.TextField(null=True, blank=True, verbose_name='Comentario')), - ('job', models.ForeignKey(to='jobs.Job', on_delete=models.CASCADE)), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/jobs/migrations/0002_auto_20170326_1616.py b/jobs/migrations/0002_auto_20170326_1616.py deleted file mode 100644 index 038e35c3..00000000 --- a/jobs/migrations/0002_auto_20170326_1616.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -import autoslug.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='job', - name='slug', - field=autoslug.fields.AutoSlugField(populate_from='title', unique=True, editable=False), - ), - ] diff --git a/jobs/migrations/0003_normalize_tags_20180428_1955.py b/jobs/migrations/0003_normalize_tags_20180428_1955.py deleted file mode 100644 index 3f3c6d2a..00000000 --- a/jobs/migrations/0003_normalize_tags_20180428_1955.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -from django.db import migrations, models -from taggit.models import TaggedItem, Tag # can be used since Models are actually not modified -from django.contrib.contenttypes.models import ContentType - -from jobs.utils import normalize - - -def clean_tags(apps, schema_editor): - """Normalize all tags, fix tagged-objects and remove (now unused) not-normalized tags.""" - - all_tags = Tag.objects.all() - n_tags = all_tags.count() - for bad_tag in all_tags: - normalized = normalize(bad_tag.name) - if normalized == bad_tag.name: - # Actually a good tabg - continue - normalized_tag, _ = Tag.objects.get_or_create(name=normalized) - tagged_objects = TaggedItem.objects.filter(tag__id=bad_tag.id) - for o in tagged_objects: - # o.tags.add(normalized) - TaggedItem.objects.create( - tag=normalized_tag, - content_type=ContentType.objects.get_for_model(o), - object_id=o.id - ) - Tag.objects.filter(id=bad_tag.id).delete() - - annotated = Tag.objects.annotate(count=models.Count('taggit_taggeditem_items')) - annotated.filter(count=0).delete() - print(" Deleted {} tags".format(n_tags - Tag.objects.count()), end='') - - -class Migration(migrations.Migration): - dependencies = [ - ('jobs', '0002_auto_20170326_1616'), - ('taggit', '0002_auto_20150616_2121') - ] - - operations = [ - migrations.RunPython(clean_tags, reverse_code=migrations.RunPython.noop), - ] diff --git a/jobs/migrations/__init__.py b/jobs/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/jobs/models.py b/jobs/models.py deleted file mode 100644 index 7c500609..00000000 --- a/jobs/models.py +++ /dev/null @@ -1,107 +0,0 @@ -from autoslug import AutoSlugField -from django.conf import settings -from django.db import models -from django.urls import reverse -from django.utils.translation import gettext as _ -from pycompanies.models import Company -from taggit_autosuggest.managers import TaggableManager -from model_utils.models import TimeStampedModel - -JOB_SENIORITIES = ( - ('Trainee', 'Trainee'), - ('Junior', 'Junior'), - ('Semi Senior', 'Semi Senior'), - ('Senior', 'Senior'), -) - - -class JobQuerySet(models.QuerySet): - - # Companies have a default rank of 0, sponsored ones greater than 0 - MIN_RANK = 0 - - def actives(self): - # -- only active records - return self.filter(is_active=True) - - def sponsored(self, from_date, sponsored_size): - sponsored_jobs = self.actives().filter( - created__gte=from_date, - company__rank__gt=self.MIN_RANK).order_by( - '-company__rank', '-created')[:sponsored_size] - return sponsored_jobs - - def non_sponsored(self, from_date, sponsored_size): - sponsored_jobs = self.sponsored(from_date, sponsored_size) - return self.actives().exclude(pk__in=sponsored_jobs) - - -class Job(models.Model): - """A PyAr Job.""" - - title = models.CharField(max_length=255, verbose_name=_('Título')) - company = models.ForeignKey(Company, - null=True, - blank=True, - verbose_name=_('Empresa'), - on_delete=models.CASCADE) - description = models.TextField(verbose_name=_('Descripción')) - location = models.CharField(max_length=100, verbose_name=_('Lugar')) - email = models.EmailField() - owner = models.ForeignKey(settings.AUTH_USER_MODEL, - on_delete=models.CASCADE) - created = models.DateTimeField(auto_now_add=True) - modified = models.DateTimeField(auto_now=True) - tags = TaggableManager(verbose_name=_('Etiquetas')) - remote_work = models.BooleanField( - default=False, - verbose_name=_('Trabajo Remoto')) - seniority = models.CharField( - max_length=100, - blank=True, - default='', - choices=JOB_SENIORITIES, - verbose_name=_('Experiencia')) - slug = AutoSlugField(populate_from='title', unique=True) - is_active = models.BooleanField(default=True) - - objects = JobQuerySet.as_manager() - - def __str__(self): - return u'{0}'.format(self.title) - - @property - def is_remote_work_allowed(self): - return self.remote_work - - def get_absolute_url(self): - return reverse('jobs_view', kwargs={'slug': self.slug}) - - class Meta: - ordering = ['-created'] - - -class JobInactivated(TimeStampedModel): - """ Jobs Inactivated """ - REASONS = ( - ('No es un trabajo relacionado con Python', - 'No es un trabajo relacionado con Python'), - ('Spam', 'Spam'), - ('Información insuficiente', 'Información insuficiente'), - ) - - job = models.ForeignKey(Job, on_delete=models.CASCADE) - reason = models.CharField( - max_length=100, - blank=False, - choices=REASONS, - verbose_name=_('Motivo')) - comment = models.TextField(blank=True, - null=True, - verbose_name=_('Comentario')) - - def __str__(self): - return u'%s' % self.job.title - - def get_absolute_url(self): - return reverse('jobs_list_all') diff --git a/jobs/tests/__init__.py b/jobs/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/jobs/tests/factories.py b/jobs/tests/factories.py deleted file mode 100644 index 0b18955d..00000000 --- a/jobs/tests/factories.py +++ /dev/null @@ -1,26 +0,0 @@ -from factory import SubFactory, Sequence, post_generation -from factory.django import DjangoModelFactory - -from events.tests.factories import UserFactory -from jobs.models import Job - - -class JobFactory(DjangoModelFactory): - class Meta: - model = Job - - owner = SubFactory(UserFactory) - title = Sequence(lambda n: 'job_title_%i' % n) - - @post_generation - def set_created(obj, create, extracted, **kwargs): - """ - Update the creation time of the built instance. As it is an auto-generated field, we must - set its value after creation. - - To use: JobFactory(set_created='1985-10-26 09:00Z') - - """ - if extracted: - obj.created = extracted - obj.save() diff --git a/jobs/tests/test_utils.py b/jobs/tests/test_utils.py deleted file mode 100644 index 6da9dc5d..00000000 --- a/jobs/tests/test_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.test import TestCase - -from jobs.utils import normalize_tags - - -class UtilTest(TestCase): - def test_normalize_tags_with_repeated(self): - repeated_tags = ['Django', 'DJANGO', 'djAngo'] - tags = normalize_tags(repeated_tags) - self.assertEqual(len(tags), 1) - - def test_normalize_tags_with_repeated_and_spaces(self): - repeated_tags = ['Django', ' DJANGO', 'djAngo '] - tags = normalize_tags(repeated_tags) - self.assertEqual(len(tags), 1) - - def test_normalize_tags_with_non_ascii(self): - repeated_tags = ['DñàÈ', ] - tags = list(normalize_tags(repeated_tags)) - self.assertEqual(len(tags), 1) - self.assertTrue(tags[0].islower()) - self.assertTrue('dnae' == tags[0]) diff --git a/jobs/tests/test_views.py b/jobs/tests/test_views.py deleted file mode 100644 index 1b68977e..00000000 --- a/jobs/tests/test_views.py +++ /dev/null @@ -1,139 +0,0 @@ -import bleach - -from django.test import TestCase, Client -from django.urls import reverse - -from jobs.models import Job -from jobs.tests.factories import JobFactory -from events.tests.factories import UserFactory -from pycompanies.tests.factories import CompanyFactory - - -class JobsTest(TestCase): - def setUp(self): - self.user = UserFactory() - self.client = Client() - self.client.login(username=self.user.username, password='secret') - - def test_jobs_view_list(self): - job = JobFactory(owner=self.user) - company = CompanyFactory(rank=3) - sponsored_job = JobFactory(owner=self.user, company=company) - sponsored_job2 = JobFactory(owner=self.user, company=company) - - response = self.client.get(reverse('jobs_list_all')) - self.assertEqual(response.status_code, 200) - self.assertIn(job, response.context["job_list"]) - self.assertEqual(len(response.context["job_list"]), 1) - self.assertIn(sponsored_job, response.context["sponsored_jobs"]) - self.assertIn(sponsored_job2, response.context["sponsored_jobs"]) - self.assertEqual(len(response.context["sponsored_jobs"]), 2) - - def test_jobs_view_list_with_tags(self): - job = JobFactory(owner=self.user) - job_2 = JobFactory(owner=self.user) - - job.tags.add('tag1') - job_2.tags.add('tag2') - - response = self.client.get(reverse('jobs_list_all'), {'tag_tag1': 1}) - self.assertEqual(response.status_code, 200) - self.assertIn(job, response.context["job_list"]) - self.assertEqual(len(response.context["job_list"]), 1) - - def test_jobs_view_list_regular_and_sponsored(self): - sponsored_company = CompanyFactory(name='Name', rank=3) - sponsored_job = JobFactory(owner=self.user, company=sponsored_company) - sponsored_job_2 = JobFactory(owner=self.user, company=sponsored_company) - - company = CompanyFactory(name='Other name', rank=0) - job = JobFactory(owner=self.user, company=company) - job_2 = JobFactory(owner=self.user, company=company) - - response = self.client.get(reverse('jobs_list_all')) - self.assertEqual(response.status_code, 200) - self.assertIn(job, response.context["job_list"]) - self.assertIn(job_2, response.context["job_list"]) - self.assertEqual(len(response.context["job_list"]), 2) - self.assertIn(sponsored_job, response.context["sponsored_jobs"]) - self.assertIn(sponsored_job_2, response.context["sponsored_jobs"]) - self.assertEqual(len(response.context["sponsored_jobs"]), 2) - - def test_jobs_view_create(self): - response = self.client.get(reverse('jobs_add')) - job = { - 'title': 'Python Dev', - 'location': 'Bahia Blanca', - 'email': 'info@undominio.com', - 'tags': 'python,remoto,django', - 'description': 'Buscamos desarrollador python freelance.' - } - response = self.client.post(reverse('jobs_add'), job) - self.assertEqual(response.status_code, 302) - self.assertEqual(Job.objects.filter(title='Python Dev').count(), 1) - - def test_jobs_view_create_avoiding_repeated_tags(self): - response = self.client.get(reverse('jobs_add')) - job = { - 'title': 'Python Dev', - 'location': 'Bahia Blanca', - 'email': 'info@undominio.com', - 'tags': 'python,remoto,DJANGO,django', - 'description': 'Buscamos desarrollador python freelance.' - } - response = self.client.post(reverse('jobs_add'), job) - self.assertEqual(response.status_code, 302) - self.assertEqual(response.status_code, 302) - self.assertEqual(Job.objects.filter(title='Python Dev').count(), 1) - self.assertEqual(Job.objects.all()[0].tags.all().count(), 3) - - def test_jobs_view_edit(self): - job = JobFactory( - owner=self.user, title='Python Dev', - description='Buscamos desarrollador python freelance', - location='Bahia Blanca', email='info@undominio') - - response = self.client.get(reverse('jobs_update', args=(job.pk, ))) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.context["object"], job) - job_data = { - 'title': 'Python Dev 2', - 'location': 'Azul', - 'email': 'info@undominio.com', - 'tags': 'python,remoto,django', - 'description': 'Buscamos desarrollador python freelance.' - } - response = self.client.post(reverse('jobs_update', args=(job.pk, )), job_data) - self.assertEqual(response.status_code, 302) - self.assertFalse(Job.objects.filter(title='Python Dev').exists()) - edit_job = Job.objects.get(pk=job.pk) - self.assertEqual(edit_job.location, "Azul") - self.assertEqual(edit_job.title, "Python Dev 2") - - def test_jobs_view_idelete(self): - job = JobFactory( - owner=self.user, title='Python/Django Dev', - description='Buscamos desarrollador python freelance', - location='General Pico', email='info@fdq.com') - - response = self.client.get(reverse('jobs_delete', args=(job.pk, ))) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.context["object"], job) - - response = self.client.post(reverse('jobs_delete', args=(job.pk, ))) - self.assertEqual(response.status_code, 302) - self.assertFalse(Job.objects.filter(title='Python/Django Dev').exists()) - - def test_html_sanitizer_in_description_field(self): - response = self.client.get(reverse('jobs_add')) - job = { - 'title': 'Python Dev', - 'location': 'Cruz del Eje', - 'email': 'info@undominio.com', - 'tags': 'python,remoto,django', - 'description': 'an example' - } - response = self.client.post(reverse('jobs_add'), job) - self.assertEqual(response.status_code, 302) - job = Job.objects.get(title='Python Dev') - self.assertEqual(job.description, bleach.clean('an example')) diff --git a/jobs/urls.py b/jobs/urls.py deleted file mode 100644 index dda59050..00000000 --- a/jobs/urls.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.contrib.auth.decorators import login_required -from django.views.generic.detail import DetailView -from django.urls import re_path -from .models import Job -from .views import (JobCreate, JobList, JobDelete, JobUpdate, JobsFeed, - JobInactivate) - - -urlpatterns = [ - re_path(r'^$', JobList.as_view(), name='jobs_list_all'), - re_path(r'^rss$', JobsFeed(), name='jobs_feed'), - re_path(r'^add/$', login_required(JobCreate.as_view()), name='jobs_add'), - re_path(r'^(?P[\w-]+)/$', DetailView.as_view(model=Job), name='jobs_view'), - re_path(r'^(?P\d+)/delete/$', login_required(JobDelete.as_view()), name='jobs_delete'), - re_path(r'^(?P\d+)/update/$', login_required(JobUpdate.as_view()), name='jobs_update'), - re_path(r'^(?P\d+)/inactivate/$', login_required(JobInactivate.as_view()), - name='jobs_inactivate'), -] diff --git a/jobs/utils.py b/jobs/utils.py deleted file mode 100644 index 6d06a8f0..00000000 --- a/jobs/utils.py +++ /dev/null @@ -1,15 +0,0 @@ -import unicodedata - - -def normalize(tag): - """Normalize a single tag: remove non valid chars, lower case all.""" - - tag_stripped = tag.strip() - value = unicodedata.normalize("NFKD", tag_stripped.lower()) - value = value.encode('ascii', 'ignore').decode('utf-8') - return value - - -def normalize_tags(tags): - """Parse a list of tags and removed duplicated tags and non valid chars.""" - return {normalize(tag) for tag in tags} diff --git a/jobs/views.py b/jobs/views.py deleted file mode 100644 index 24f335d6..00000000 --- a/jobs/views.py +++ /dev/null @@ -1,137 +0,0 @@ -from datetime import datetime, timedelta - -from community.views import FilterableList, OwnedObject -from django.contrib.syndication.views import Feed -from django.core.mail import EmailMessage -from django.template.loader import render_to_string -from django.urls import reverse_lazy -from django.utils.timezone import utc -from django.views.generic import ListView -from django.views.generic.edit import CreateView, DeleteView, UpdateView -from django.conf import settings - -from .forms import JobForm, JobInactivateForm -from .models import Job, JobInactivated - - -class JobActiveMixin(object): - def get_queryset(self): - """ Job must be active """ - qs = super(JobActiveMixin, self).get_queryset() - return qs.actives() - - -class JobsFeed(Feed): - title = "Feed de ofertas laborales de Pyar" - link = reverse_lazy("jobs_list_all") - description = "Todas las ofertas laborales con Python publicadas en PyAR" - - description_template = "jobs/job_detail_feed.html" - - def items(self): - return Job.objects.order_by('-created')[0:10] - - def item_title(self, item): - return item.title - - def item_pubdate(self, item): - return item.created - - def author_name(self, item): - if item and item.company: - return item.company.name - return '' - - def author_email(self, item): - if item: - return item.email - return '' - - def author_link(self, item): - if item and item.company: - return item.company.get_absolute_url() - return '' - - def categories(self, item): - if item: - return item.tags.values_list('name', flat=True) - return () - - -class JobCreate(CreateView): - model = Job - form_class = JobForm - - def form_valid(self, form): - form.instance.owner = self.request.user - form.instance.is_active = True - return super(JobCreate, self).form_valid(form) - - -class JobList(ListView, JobActiveMixin, FilterableList): - model = Job - paginate_by = 20 - ordering = ['-created'] - two_month_ago = datetime.now(tz=utc) - timedelta(days=60) - # TODO: move to some dinamic configurable place - COUNT_OF_SPONSORED = 3 - - def get_queryset(self): - qs = Job.objects.non_sponsored( - self.two_month_ago, self.COUNT_OF_SPONSORED) - return self.filter_queryset_tags(qs) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['sponsored_jobs'] = Job.objects.sponsored( - self.two_month_ago, self.COUNT_OF_SPONSORED) - return context - - -class JobUpdate(UpdateView, JobActiveMixin, OwnedObject): - - """Edit jobs that use Python.""" - model = Job - form_class = JobForm - - -class JobDelete(DeleteView, JobActiveMixin, OwnedObject): - - """Delete a Job.""" - model = Job - success_url = reverse_lazy('jobs_list_all') - - -class JobInactivate(CreateView): - """ Inactivate Job by moderator """ - - model = JobInactivated - template_name = 'jobs/job_inactivate_form.html' - form_class = JobInactivateForm - - def form_valid(self, form): - job = Job.objects.get(pk=self.kwargs['pk']) - form.instance.job = job - - # -- inactivate job - job.is_active = False - job.save() - - # -- ¿send mail to job owner? - if form.cleaned_data['send_email']: - context = { - 'job_title': job.title, - 'reason': form.cleaned_data['reason'], - 'comment': form.cleaned_data['comment'] - } - - body = render_to_string('jobs/inactivate_job_email.txt', context) - email = EmailMessage( - subject="[PyAr] Aviso de trabajo dado de baja", - to=(job.company.owner.email, ), - from_email=getattr(settings, "DEFAULT_FROM_EMAIL"), - body=body - ) - email.send() - - return super(JobInactivate, self).form_valid(form) diff --git a/pyarweb/settings/base.py b/pyarweb/settings/base.py index dfd344af..4ad1eb2f 100644 --- a/pyarweb/settings/base.py +++ b/pyarweb/settings/base.py @@ -53,7 +53,6 @@ 'community', 'news', 'pycompanies', - 'jobs', 'events', 'joboffers', # 3rd party apps diff --git a/pyarweb/urls.py b/pyarweb/urls.py index 6b84112e..93c0a725 100644 --- a/pyarweb/urls.py +++ b/pyarweb/urls.py @@ -27,7 +27,6 @@ re_path(r'^empresas/', include(('pycompanies.urls', 'pycompanies'), namespace='companies')), re_path(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), re_path(r'^summernote/', include('django_summernote.urls')), - re_path(r'^trabajo-vieja/', include('jobs.urls')), re_path(r'^admin/', admin.site.urls), re_path(r'^accounts/', include('allauth.urls')), re_path(r'^eventos/', include(('events.urls', 'events'), namespace='events')),