Drop unused jobs page.

Last job was posted on january 2022, 10 month ago.
This commit is contained in:
Julien Palard 2022-11-17 12:02:00 +01:00
parent 1da49c227e
commit 754f4178d3
Signed by: mdk
GPG Key ID: 0EFC1AC1006886F8
16 changed files with 10 additions and 335 deletions

View File

@ -47,18 +47,15 @@ def page_not_found(e):
from afpy.routes.home import home_bp from afpy.routes.home import home_bp
from afpy.routes.posts import posts_bp, post_render from afpy.routes.posts import posts_bp, post_render
from afpy.routes.jobs import jobs_bp, jobs_render
from afpy.routes.rss import rss_bp from afpy.routes.rss import rss_bp
application.register_blueprint(home_bp) application.register_blueprint(home_bp)
application.register_blueprint(posts_bp) application.register_blueprint(posts_bp)
application.register_blueprint(jobs_bp)
application.register_blueprint(rss_bp) application.register_blueprint(rss_bp)
from afpy.models.AdminUser import AdminUser, AdminUser_Admin from afpy.models.AdminUser import AdminUser, AdminUser_Admin
from afpy.models.NewsEntry import NewsEntry, NewsEntry_Admin from afpy.models.NewsEntry import NewsEntry, NewsEntry_Admin
from afpy.models.JobPost import JobPost, JobPost_Admin
from afpy.models.Slug import Slug, SlugAdmin from afpy.models.Slug import Slug, SlugAdmin
@ -66,7 +63,6 @@ from afpy.routes.admin import (
AdminIndexView, AdminIndexView,
NewAdminView, NewAdminView,
ChangePasswordView, ChangePasswordView,
JobsModerateView,
NewsModerateView, NewsModerateView,
CustomFileAdmin, CustomFileAdmin,
) )
@ -81,10 +77,8 @@ admin = Admin(
) )
# Registers the views for each table # Registers the views for each table
admin.add_view(JobsModerateView(name="Moderate Jobs", endpoint="jobs_moderation", category="Moderate"))
admin.add_view(NewsModerateView(name="Moderate News", endpoint="news_moderation", category="Moderate")) admin.add_view(NewsModerateView(name="Moderate News", endpoint="news_moderation", category="Moderate"))
admin.add_view(NewsEntry_Admin(NewsEntry)) admin.add_view(NewsEntry_Admin(NewsEntry))
admin.add_view(JobPost_Admin(JobPost))
admin.add_view(SlugAdmin(Slug)) admin.add_view(SlugAdmin(Slug))
admin.add_view(CustomFileAdmin(config.IMAGES_PATH, "/images/", name="Images Files")) admin.add_view(CustomFileAdmin(config.IMAGES_PATH, "/images/", name="Images Files"))
admin.add_view(NewAdminView(name="New Admin", endpoint="register_admin", category="Admin")) admin.add_view(NewAdminView(name="New Admin", endpoint="register_admin", category="Admin"))
@ -107,10 +101,7 @@ def get_slug_url(item):
url_root = request.url_root url_root = request.url_root
slug = item.slug.where(Slug.canonical == True).first() # noqa slug = item.slug.where(Slug.canonical == True).first() # noqa
if not slug: if not slug:
if isinstance(item, JobPost): return url_root[:-1] + "/actualites/" + str(item.id)
return url_root[:-1] + "/emplois/" + str(item.id)
else:
return url_root[:-1] + "/actualites/" + str(item.id)
else: else:
return url_root[:-1] + slug.url return url_root[:-1] + slug.url
@ -122,5 +113,3 @@ def slug_fallback(slug):
abort(404) abort(404)
if slug.newsentry: if slug.newsentry:
return post_render(slug.newsentry.id) return post_render(slug.newsentry.id)
elif slug.jobpost:
return jobs_render(slug.jobpost.id)

View File

@ -1,23 +0,0 @@
from flask_pagedown.fields import PageDownField
from flask_wtf import FlaskForm
from wtforms import FileField
from wtforms import StringField
from wtforms import validators
from wtforms.validators import DataRequired
def validate_email_or_phone(form, field):
if not form.email.data and not form.phone.data:
raise validators.ValidationError("Must have phone or email")
class JobPostForm(FlaskForm):
title = StringField("Titre", validators=[DataRequired()])
summary = StringField("Résumé (optionnel)")
content = PageDownField("Contenu de l'offre", validators=[DataRequired()])
company = StringField("Entreprise", validators=[DataRequired()])
location = StringField("Addresse", validators=[DataRequired()])
contact_info = StringField("Personne à contacter", validators=[DataRequired()])
email = StringField("Email", validators=[validate_email_or_phone])
phone = StringField("Téléphone", validators=[validate_email_or_phone])
image = FileField("Image (optionnel)")

View File

@ -1,111 +0,0 @@
from datetime import datetime
from typing import Optional
from flask_admin.contrib.peewee import ModelView
from flask_login import current_user
from peewee import CharField
from peewee import DateTimeField
from peewee import ForeignKeyField
from peewee import TextField
from afpy.models import BaseModel
from afpy.models.AdminUser import AdminUser
class JobPost(BaseModel):
title = TextField(null=False, help_text="Title of the job post", verbose_name="Title")
summary = TextField(null=True, help_text="Summary of the job post", verbose_name="Summary")
content = TextField(null=False, help_text="Content of the job post", verbose_name="Content")
dt_submitted = DateTimeField(
null=False,
default=datetime.now,
help_text="When was the job post submitted",
verbose_name="Datetime Submitted",
index=True,
)
dt_updated = DateTimeField(
null=False, default=datetime.now, help_text="When was the job post updated", verbose_name="Datetime Updated"
)
dt_published = DateTimeField(
null=True, help_text="When was the job post published", verbose_name="Datetime Published"
)
state = CharField(
null=False,
default="waiting",
choices=[("waiting", "waiting"), ("published", "published"), ("rejected", "rejected")],
help_text="Current state of the job post",
verbose_name="State",
)
approved_by = ForeignKeyField(
AdminUser,
null=True,
default=None,
backref="adminuser",
help_text="Who approved the job post",
verbose_name="Approved by",
)
company = CharField(null=False, help_text="Company that posted the job", verbose_name="Company")
phone = CharField(null=True, help_text="Phone number to contact", verbose_name="Phone Number")
location = CharField(null=False, help_text="Where is the job located", verbose_name="Job Location")
email = CharField(null=True, help_text="Email to contact", verbose_name="Email Address")
contact_info = CharField(null=False, help_text="Person to contact", verbose_name="Contact info")
image_path = CharField(null=True, help_text="Image for the job post", verbose_name="Image Path in filesystem")
@classmethod
def create(
cls,
title: str,
content: str,
company: str,
location: str,
contact_info: str,
email: Optional[str] = None,
phone: Optional[str] = None,
summary: Optional[str] = None,
dt_submitted: Optional[datetime] = None,
dt_updated: Optional[datetime] = None,
dt_published: Optional[datetime] = None,
state: str = "waiting",
approved_by: Optional[AdminUser] = None,
image_path: Optional[str] = None,
):
if not dt_submitted:
dt_submitted = datetime.now()
if not dt_updated:
dt_updated = datetime.now()
if not email and not phone:
raise ValueError("One of email or phone must be provided")
new_job = super().create(
title=title,
content=content,
company=company,
location=location,
contact_info=contact_info,
email=email,
phone=phone,
summary=summary,
dt_submitted=dt_submitted,
dt_updated=dt_updated,
dt_published=dt_published,
state=state,
approved_by=approved_by,
image_path=image_path,
)
new_job.save()
return new_job
class JobPost_Admin(ModelView):
model_class = JobPost
column_list = ("state", "title", "dt_published")
column_default_sort = ("id", True)
def is_accessible(self):
return current_user.is_authenticated
if not JobPost.table_exists():
JobPost.create_table()

View File

@ -5,13 +5,11 @@ from peewee import CharField
from peewee import ForeignKeyField from peewee import ForeignKeyField
from afpy.models import BaseModel from afpy.models import BaseModel
from afpy.models.JobPost import JobPost
from afpy.models.NewsEntry import NewsEntry from afpy.models.NewsEntry import NewsEntry
class Slug(BaseModel): class Slug(BaseModel):
url = CharField(null=False, help_text="From URL", verbose_name="From URL", unique=True, index=True) url = CharField(null=False, help_text="From URL", verbose_name="From URL", unique=True, index=True)
jobpost = ForeignKeyField(JobPost, backref="slug", null=True)
newsentry = ForeignKeyField(NewsEntry, backref="slug", null=True) newsentry = ForeignKeyField(NewsEntry, backref="slug", null=True)
canonical = BooleanField(default=True) canonical = BooleanField(default=True)

View File

@ -19,7 +19,6 @@ from afpy.forms.auth import ChangePasswordForm
from afpy.forms.auth import LoginForm from afpy.forms.auth import LoginForm
from afpy.forms.auth import RegistrationForm from afpy.forms.auth import RegistrationForm
from afpy.models.AdminUser import AdminUser from afpy.models.AdminUser import AdminUser
from afpy.models.JobPost import JobPost
from afpy.models.NewsEntry import NewsEntry from afpy.models.NewsEntry import NewsEntry
@ -129,11 +128,6 @@ class _ModerateView(admin.BaseView):
return redirect(url_for(".moderate_view")) return redirect(url_for(".moderate_view"))
class JobsModerateView(_ModerateView):
model = JobPost
edit_view = "jobpost.edit_view"
class NewsModerateView(_ModerateView): class NewsModerateView(_ModerateView):
model = NewsEntry model = NewsEntry
edit_view = "newsentry.edit_view" edit_view = "newsentry.edit_view"

View File

@ -8,7 +8,6 @@ from flask import render_template
from flask import send_from_directory from flask import send_from_directory
from peewee import DoesNotExist from peewee import DoesNotExist
from afpy.models.JobPost import JobPost
from afpy.models.NewsEntry import NewsEntry from afpy.models.NewsEntry import NewsEntry
from afpy import config from afpy import config
@ -56,9 +55,6 @@ def status():
"actualites": { "actualites": {
"waiting": NewsEntry.select().where(NewsEntry.state == "waiting").count(), "waiting": NewsEntry.select().where(NewsEntry.state == "waiting").count(),
}, },
"emplois": {
"waiting": JobPost.select().where(JobPost.state == "waiting").count(),
},
} }

View File

@ -18,33 +18,15 @@ jobs_bp = Blueprint("jobs", __name__)
@jobs_bp.route("/emplois/<int:post_id>") @jobs_bp.route("/emplois/<int:post_id>")
def jobs_render(post_id: int): def jobs_render(post_id: int):
try: return redirect("https://discuss.afpy.org/c/emplois/14")
job = JobPost.get_by_id(post_id)
except DoesNotExist:
abort(404)
return render_template("pages/job.html", body_id="emplois", job=job, name=job.title)
@jobs_bp.route("/emplois") @jobs_bp.route("/emplois")
@jobs_bp.route("/emplois/page/<int:current_page>") @jobs_bp.route("/emplois/page/<int:current_page>")
def jobs_page(current_page: int = 1): def jobs_page(current_page: int = 1):
total_pages = (JobPost.select().where(JobPost.state == "published").count() // config.NEWS_PER_PAGE) + 1 return redirect("https://discuss.afpy.org/c/emplois/14")
jobs = (
JobPost.select()
.where(JobPost.state == "published")
.order_by(JobPost.dt_submitted.desc())
.paginate(current_page, config.NEWS_PER_PAGE)
)
return render_template(
"pages/jobs.html",
body_id="emplois",
jobs=jobs,
title="Offres d'emploi",
current_page=current_page,
total_pages=total_pages,
)
@jobs_bp.route("/emplois/new", methods=["GET"]) @jobs_bp.route("/emplois/new", methods=["GET"])
def new_job(): def new_job():
return render_template("pages/edit_job.html") return redirect("https://discuss.afpy.org/c/emplois/14")

View File

@ -6,8 +6,8 @@ from flask import abort
from flask import Blueprint from flask import Blueprint
from flask import render_template from flask import render_template
from flask import url_for from flask import url_for
from flask import redirect
from afpy.models.JobPost import JobPost
from afpy.models.NewsEntry import NewsEntry from afpy.models.NewsEntry import NewsEntry
from afpy import config from afpy import config
@ -20,8 +20,7 @@ def feed_rss(type):
name = "" name = ""
entries = [] entries = []
if type == "emplois": if type == "emplois":
name = "Emplois" return redirect("https://discuss.afpy.org/c/emplois/14.rss")
entries = JobPost.select().where(JobPost.state == "published")
elif type == "actualites": elif type == "actualites":
name = "Actualités" name = "Actualités"
entries = NewsEntry.select().where(NewsEntry.state == "published") entries = NewsEntry.select().where(NewsEntry.state == "published")

View File

@ -4,7 +4,7 @@
(url_for('home.home_page'), 'index', 'Accueil'), (url_for('home.home_page'), 'index', 'Accueil'),
(url_for('home.render_rest', name='a-propos'), 'a-propos', 'Qui sommes-nous ?'), (url_for('home.render_rest', name='a-propos'), 'a-propos', 'Qui sommes-nous ?'),
(url_for('posts.posts_page'), 'actualites', 'Actualités'), (url_for('posts.posts_page'), 'actualites', 'Actualités'),
(url_for('jobs.jobs_page'), 'emplois', 'Offres d\'emplois'), ("https://discuss.afpy.org/c/emplois/14", 'emplois', 'Offres d\'emplois'),
(url_for('home.community_page'), 'communaute', 'Communauté'), (url_for('home.community_page'), 'communaute', 'Communauté'),
('https://discuss.afpy.org', 'discussion', 'Discussion'), ('https://discuss.afpy.org', 'discussion', 'Discussion'),
('https://www.afpy.org/discord', 'discord', 'Discord'), ('https://www.afpy.org/discord', 'discord', 'Discord'),

View File

@ -7,7 +7,7 @@
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<h1>AFPy Backend Admin</h1> <h1>AFPy Backend Admin</h1>
<p class="lead"> <p class="lead">
Bienvenue dans le backoffice de l'AFPy. Vous avez accès à la création, modification et suppression d'articles, jobs et admins, ainsi qu'aux tables.<br> Bienvenue dans le backoffice de l'AFPy. Vous avez accès à la création, modification et suppression d'articles et admins, ainsi qu'aux tables.<br>
Ne touchez à rien si vous ne savez pas ce que vous faites. Ne touchez à rien si vous ne savez pas ce que vous faites.
</p> </p>
{% else %} {% else %}

View File

@ -1,23 +0,0 @@
{% extends '_parts/base.jinja2' %}
{% block header %}
<h1>Création d'un job</h1>
{% endblock header %}
{% block main %}
<article>
<h2>Pour plus de visibilité, la job board de l'AFPy bouge ici : <a href="https://discuss.afpy.org/c/emplois/14">https://discuss.afpy.org/c/emplois</a> !</h2>
<p>
Pour créer une offre d'emploi là bas, deux solutions :<br/>
<ul>
<li>Envoyer un email à jobs at afpy point org, le titre de votre offre en sujet, le message de votre offre en corps de message, c'est tout.</li>
<li>Publier l'offre directement <a href="https://discuss.afpy.org">sur le forum</a>, dans la catégorie « <a href="https://discuss.afpy.org/c/emplois/14">Offres d'emploi</a> » en cliquant sur le bouton « Créer un sujet »</li>
</ul>
</p>
<h2>C'est quoi https://discuss.afpy.org?</h2>
<p>
C'est une instance de <a href="https://discourse.org">Discourse</a>, un forum <a href="https://github.com/discourse/discourse">open source</a> qu'on héberge en france chez <a href="https://www.gandi.net/fr">Gandi</a>.
</p>
</article>
{% endblock main %}

View File

@ -1,51 +0,0 @@
{% extends '_parts/base.jinja2' %}
{% block header %}
<h1>{{ job.title }}</h1>
{% endblock header %}
{% block main %}
{% if preview %}
<a href="{{ url_for("jobs_moderation.moderate_view") }}">Retour à l'interface d'administration</a>
{# <p><a class="btn btn-warning" href="http://127.0.0.1:5000/admin/jobpost/edit/?id={{ job.id }}" role="button">Edit</a></p>#}
{% endif %}
<article>
{% if preview %}
<time pubdate datetime="">
Pas publié
</time>
{% else %}
<time pubdate datetime="{{ job.dt_published }}">
Posté le {{ job.dt_published.strftime('%d/%m/%Y') }} à {{ job.dt_published.strftime('%H:%M:%S') }}
</time>
{% endif %}
<p>
<em>
{{ job.summary | safe if job.summary }}
</em>
</p>
{% if job.image_path %}
<img src="{{ url_for('home.get_image', path=job.image_path) }}" alt="{{ job.title }}" />
{% endif %}
{{ job.content | md2html | safe }}
<aside>
<h2>{{ job.company or "(Société inconnue)" }}</h2>
<dl>
<dt>Adresse</dt>
<dd>{{ job.location }}</dd>
<dt>Personne à contacter</dt>
<dd>{{ job.contact_info }}</dd>
{% if job.phone %}
<dt>Téléphone</dt>
<dd><a href="tel:{{ job.phone }}">{{ job.phone }}</a></dd>
{% endif %}
{% if job.email %}
<dt>Adresse e-mail</dt>
<dd><a href="mailto:{{ job.email }}">{{ job.email }}</a></dd>
{% endif %}
</dl>
</aside>
</article>
{% endblock main %}

View File

@ -1,41 +0,0 @@
{% extends '_parts/base.jinja2' %}
{% block header %}
<h1>{{ title }}</h1>
{% endblock header %}
{% block main %}
<aside>
<h2>Pour plus de visibilité, la job board de l'AFPy bouge ici : <a href="https://discuss.afpy.org/c/emplois/14">https://discuss.afpy.org/c/emplois</a> !</h2>
<p>
Pour créer une offre d'emploi là bas, deux solutions :<br/>
<ul>
<li>Envoyer un email à jobs at afpy point org, le titre de votre offre en sujet, le message de votre offre en corps de message, c'est tout.</li>
<li>Publier l'offre directement <a href="https://discuss.afpy.org">sur le forum</a>, dans la catégorie « <a href="https://discuss.afpy.org/c/emplois/14">Offres d'emploi</a> » en cliquant sur le bouton « Créer un sujet »</li>
</ul>
</p>
</aside>
{% for job in jobs %}
<article>
<h2>{{ job.title }}</h2>
<time pubdate datetime="{{ job.dt_published }}">
{{ job.dt_published.strftime('%d/%m/%Y') }}
</time>
{% if job.image_path %}
<img src="{{ url_for('home.get_image', path=job.image_path) }}" alt="{{ job.title }}" />
{% endif %}
{% if job.summary %}{{ job.summary |safe }}{% else %}{{ job.content|truncate(50)|safe }}{% endif %}
<p><a href="{{ job|slug_url }}">Lire la suite…</a></p>
</article>
{% endfor %}
<aside>
{% if current_page != 1 %}
<a href="{{ url_for('jobs.jobs_page', current_page=(current_page - 1)) }}">Précedente</a>
{% endif %}
Page {{ current_page }}/{{ total_pages }}
{% if current_page != total_pages %}
<a href="{{ url_for('jobs.jobs_page', current_page=(current_page + 1)) }}">Suivante</a>
{% endif %}
</aside>
{% endblock main %}

View File

@ -7,7 +7,6 @@
{% block main %} {% block main %}
{% if preview %} {% if preview %}
<a href="{{ url_for("news_moderation.moderate_view") }}">Retour à l'interface d'administration</a> <a href="{{ url_for("news_moderation.moderate_view") }}">Retour à l'interface d'administration</a>
{# <p><a class="btn btn-warning" href="http://127.0.0.1:5000/admin/jobpost/edit/?id={{ post.id }}" role="button">Edit</a></p>#}
{% endif %} {% endif %}
<article> <article>

View File

@ -8,11 +8,7 @@
<item> <item>
<title><![CDATA[ {{ entry.title | safe }} ]]></title> <title><![CDATA[ {{ entry.title | safe }} ]]></title>
<description><![CDATA[ {{ (entry.description or entry.summary) | safe }} ]]></description> <description><![CDATA[ {{ (entry.description or entry.summary) | safe }} ]]></description>
{% if type == "emplois" %} <link>{{ url_for("posts.post_render", post_id=entry.id, _external=True) }}</link>
<link>{{ url_for("jobs.jobs_render", post_id=entry.id, _external=True) }}</link>
{% else %}
<link>{{ url_for("posts.post_render", post_id=entry.id, _external=True) }}</link>
{% endif %}
<pubDate>{{ entry.dt_published }}</pubDate> <pubDate>{{ entry.dt_published }}</pubDate>
</item> </item>
{% endfor %} {% endfor %}

View File

@ -8,16 +8,14 @@ from dateutil.parser import parse
from html2text import html2text from html2text import html2text
from afpy.models.AdminUser import AdminUser from afpy.models.AdminUser import AdminUser
from afpy.models.JobPost import JobPost
from afpy.models.NewsEntry import NewsEntry from afpy.models.NewsEntry import NewsEntry
from afpy.models.Slug import Slug from afpy.models.Slug import Slug
PAGINATION = 12 PAGINATION = 12
CATEGORY_ACTUALITIES = "actualites" CATEGORY_ACTUALITIES = "actualites"
CATEGORY_JOBS = "emplois"
CATEGORIES = {CATEGORY_ACTUALITIES: "Actualités", CATEGORY_JOBS: "Offres demploi"} CATEGORIES = {CATEGORY_ACTUALITIES: "Actualités"}
STATE_WAITING = "waiting" STATE_WAITING = "waiting"
STATE_PUBLISHED = "published" STATE_PUBLISHED = "published"
@ -110,30 +108,3 @@ if __name__ == "__main__":
post_id = post.get("id") post_id = post.get("id")
if post_id: if post_id:
Slug.create(url=post_id.split("afpy.org")[-1], newsentry=new_post) Slug.create(url=post_id.split("afpy.org")[-1], newsentry=new_post)
else:
email = post.get("email", "")
phone = post.get("phone", "")
if not email and not phone:
phone = "(no phone)"
new_job = JobPost.create(
title=post.get("title", "(untitled)"),
summary=post.get("summary"),
content=html2text(post.get("content", "")),
company=post.get("company", ""),
email=email,
phone=phone,
location=post.get("address", ""),
contact_info=post.get("contact", ""),
dt_published=parse(post.get("published")).replace(tzinfo=None)
if state == "published"
else None,
dt_submitted=parse(post.get("published")).replace(tzinfo=None),
dt_updated=parse(post.get("published")).replace(tzinfo=None),
state=state,
approved_by=admin_1 if state == "published" or state == "rejected" else None,
image_path=post.get("image"),
)
Slug.create(url=f"/posts/emplois/{post.get(FIELD_TIMESTAMP)}", jobpost=new_job)
post_id = post.get("id")
if post_id:
Slug.create(url=post_id.split("afpy.org")[-1], jobpost=new_job)