mirror of
https://github.com/kristoferssolo/FOSSDB.git
synced 2025-10-21 17:50:35 +00:00
commit
27721e277d
@ -1,3 +0,0 @@
|
|||||||
# from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
@ -2,5 +2,5 @@ Django==4.2.2
|
|||||||
Pillow==9.5.0
|
Pillow==9.5.0
|
||||||
fontawesomefree==6.4.0
|
fontawesomefree==6.4.0
|
||||||
mysqlclient==2.1.1
|
mysqlclient==2.1.1
|
||||||
django-filter==23.2
|
|
||||||
django-tailwind==3.6.0
|
django-tailwind==3.6.0
|
||||||
|
django-search-views==0.3.7
|
||||||
|
|||||||
34
setup.cfg
34
setup.cfg
@ -1,34 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = FOSSDB-Web
|
|
||||||
description = Free and Open-Source Software Database Website
|
|
||||||
author = Kristofers Solo
|
|
||||||
license = GPL3
|
|
||||||
license_files = LICENSE
|
|
||||||
platforms = unix, linux, osx, cygwin, win32
|
|
||||||
classifiers =
|
|
||||||
Programming Language :: Python :: 3.10
|
|
||||||
|
|
||||||
[options]
|
|
||||||
packages = FOSSDB_web
|
|
||||||
install_requires =
|
|
||||||
Django>=4.1
|
|
||||||
fontawesomefree>=6.2.1
|
|
||||||
mysqlclient>=2.1.1
|
|
||||||
|
|
||||||
python_requires = >=3.10
|
|
||||||
package_dir = =.
|
|
||||||
zip_safe = no
|
|
||||||
|
|
||||||
[options.extras_require]
|
|
||||||
testing =
|
|
||||||
flake8>=6.0.0
|
|
||||||
mypy>=0.991
|
|
||||||
pytest-cov>=4.0.0
|
|
||||||
pytest>=7.2.0
|
|
||||||
tox>=3.27.1
|
|
||||||
|
|
||||||
[options.package_data]
|
|
||||||
detector = py.typed
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
max-line-length = 160
|
|
||||||
@ -38,10 +38,9 @@ INSTALLED_APPS = [
|
|||||||
"main",
|
"main",
|
||||||
"account",
|
"account",
|
||||||
"fossdb",
|
"fossdb",
|
||||||
"django_filters",
|
"search_views",
|
||||||
"tailwind",
|
"tailwind",
|
||||||
"tokyonight_night",
|
"tokyonight_night",
|
||||||
"crispy_forms",
|
|
||||||
"fontawesomefree",
|
"fontawesomefree",
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
@ -51,6 +50,13 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TAILWIND_APP_NAME = "tokyonight_night"
|
||||||
|
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
"127.0.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
@ -157,12 +163,6 @@ LOGOUT_REDIRECT_URL = "/"
|
|||||||
LOGIN_URL = "/login/"
|
LOGIN_URL = "/login/"
|
||||||
|
|
||||||
|
|
||||||
TAILWIND_APP_NAME = "tokyonight_night"
|
|
||||||
|
|
||||||
INTERNAL_IPS = [
|
|
||||||
"127.0.0.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
# HTTPS settings
|
# HTTPS settings
|
||||||
# SESSION_COOKIE_SECURE = True
|
# SESSION_COOKIE_SECURE = True
|
||||||
# CSRF_COOKIE_SECURE = True
|
# CSRF_COOKIE_SECURE = True
|
||||||
|
|||||||
@ -19,10 +19,9 @@ from django.urls import include, path
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("", include("fossdb.urls")),
|
|
||||||
path("", include("main.urls")),
|
path("", include("main.urls")),
|
||||||
path("", include("account.urls")),
|
path("auth/", include("account.urls")),
|
||||||
path("", include("django.contrib.auth.urls")),
|
path("<str:owner>/<str:project_name>/", include("fossdb.urls")),
|
||||||
]
|
]
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|||||||
@ -1,12 +1,31 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
from django.contrib.auth.forms import AuthenticationForm, UserChangeForm as BaseUserChangeForm, UserCreationForm
|
||||||
|
|
||||||
from .models import User
|
from .models import Profile, User
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(AuthenticationForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(LoginForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields["username"].widget = forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Username",
|
||||||
|
"class": "verify form-field submit-form",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fields["username"].label = ""
|
||||||
|
self.fields["password"].widget = forms.PasswordInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Password",
|
||||||
|
"class": "verify form-field submit-form",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fields["password"].label = ""
|
||||||
|
|
||||||
|
|
||||||
class SignUpForm(UserCreationForm):
|
class SignUpForm(UserCreationForm):
|
||||||
email = forms.EmailField(required=False, help_text="Optional.")
|
email = forms.EmailField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
@ -16,3 +35,99 @@ class SignUpForm(UserCreationForm):
|
|||||||
"password1",
|
"password1",
|
||||||
"password2",
|
"password2",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
"username": forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Username",
|
||||||
|
"class": "verify form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"email": forms.EmailInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Email (optional)",
|
||||||
|
"class": "verify form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"password1": forms.PasswordInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Password",
|
||||||
|
"class": "verify form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"password2": forms.PasswordInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Confirm password",
|
||||||
|
"class": "verify form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"username": "",
|
||||||
|
"email": "",
|
||||||
|
"password1": "",
|
||||||
|
"password2": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserChangeForm(BaseUserChangeForm):
|
||||||
|
class Meta(BaseUserChangeForm.Meta):
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
"username",
|
||||||
|
"email",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
)
|
||||||
|
widgets = {
|
||||||
|
"username": forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Username",
|
||||||
|
"class": "form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"email": forms.EmailInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Email",
|
||||||
|
"class": "form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"first_name": forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "First Name",
|
||||||
|
"class": "form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"last_name": forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Last Name",
|
||||||
|
"class": "form-field submit-form",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"username": "",
|
||||||
|
"email": "",
|
||||||
|
"first_name": "",
|
||||||
|
"last_name": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
help_text = {
|
||||||
|
"username": None,
|
||||||
|
"email": None,
|
||||||
|
"first_name": None,
|
||||||
|
"last_name": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Profile
|
||||||
|
fields = ("picture",)
|
||||||
|
labels = {
|
||||||
|
"picture": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
help_text = {
|
||||||
|
"picture": None,
|
||||||
|
}
|
||||||
|
|||||||
@ -8,25 +8,13 @@
|
|||||||
<form method="post"
|
<form method="post"
|
||||||
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="text"
|
{{ form }}
|
||||||
placeholder="Username"
|
|
||||||
name="username"
|
|
||||||
value="{{ form.username.value|default:'' }}"
|
|
||||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
|
||||||
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
|
|
||||||
<input type="password"
|
|
||||||
placeholder="Password"
|
|
||||||
name="password"
|
|
||||||
value="{{ form.username.value|default:'' }}"
|
|
||||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
|
||||||
{% if form.password.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password.errors }}</p>{% endif %}
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
id="submit-button"
|
id="submit-button"
|
||||||
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default">
|
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200">Login</button>
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
<p>
|
<p>
|
||||||
Don't have an account? Create one <a class="underline text-skyblue-300" href="{% url 'signup' %}">Here</a>!
|
Don't have an account? Create one <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-300 ease-linear"
|
||||||
|
href="{% url 'signup' %}">Here</a>!
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
16
src/apps/account/templates/password.html
Normal file
16
src/apps/account/templates/password.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block meta %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="py-8 px-32">
|
||||||
|
<form method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
||||||
|
<h2 class="text-3xl font-abel">Change password</h2>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<button type="submit" class="button bg-skyblue-300 text-gray-500">Change</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -3,8 +3,28 @@
|
|||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
{% block meta %}{% endblock %}
|
{% block meta %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ user.username }}</h1>
|
<div class="py-8 px-32">
|
||||||
<img src="{{ user.profile_picture.url }}"
|
<div class="flex justify-end gap-4">
|
||||||
alt="{{ user.username }}’s profile picture" />
|
<a href="{% url 'logout' %}">
|
||||||
<p>{{ user.email }}</p>
|
<i class="fa-solid fa-right-from-bracket fa-2xl"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'settings' %}">
|
||||||
|
<i class="fa-solid fa-gear fa-2xl"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1 class="font-abel text-4xl mb-8">My Projects</h1>
|
||||||
|
<div class="grid grid-cols-1 gap-4">
|
||||||
|
{% for project in projects %}
|
||||||
|
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
|
||||||
|
<a href="{{ project.get_absolute_url }}"
|
||||||
|
class="hover:text-steelblue-100 transition duration-300 ease-linear">
|
||||||
|
<h2 class="font-abel text-3xl">{{ project.name }}</h2>
|
||||||
|
</a>
|
||||||
|
<p class="max-w-2xl text-justify">{{ project.description|slice:500 }}</p>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-center font-abel text-xl">No projects yet.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
24
src/apps/account/templates/setting.html
Normal file
24
src/apps/account/templates/setting.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block meta %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="py-8 px-32">
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<a href="{% url 'change_password' %}">
|
||||||
|
<i class="fa-solid fa-shield fa-2xl"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<form method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
class="flex flex-col items-center justify-center space-y-4 my-auto">
|
||||||
|
<h2 class="text-3xl font-abel">Edit profile</h2>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ user_form.username }}
|
||||||
|
{{ user_form.email }}
|
||||||
|
{{ user_form.first_name }}
|
||||||
|
{{ user_form.last_name }}
|
||||||
|
<button type="submit" class="button bg-skyblue-300 text-gray-500">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -13,33 +13,31 @@
|
|||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
name="username"
|
name="username"
|
||||||
value="{{ form.username.value|default:'' }}"
|
value="{{ form.username.value|default:'' }}"
|
||||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
class="verify form-field submit-form" />
|
||||||
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
|
{% if form.username.errors %}<p class="text-indianred-100 text-xs italic">{{ form.username.errors }}</p>{% endif %}
|
||||||
<input type="email"
|
<input type="email"
|
||||||
placeholder="Email (optional)"
|
placeholder="Email (optional)"
|
||||||
name="email"
|
name="email"
|
||||||
value="{{ form.email.value|default:'' }}"
|
value="{{ form.email.value|default:'' }}"
|
||||||
class="mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
class="form-field submit-form" />
|
||||||
{% if form.email.errors %}<p class="text-indianred-100 text-xs italic">{{ form.email.errors }}</p>{% endif %}
|
{% if form.email.errors %}<p class="text-indianred-100 text-xs italic">{{ form.email.errors }}</p>{% endif %}
|
||||||
<input type="password"
|
<input type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
name="password1"
|
name="password1"
|
||||||
value="{{ form.password1.value|default:'' }}"
|
value="{{ form.password1.value|default:'' }}"
|
||||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
class="verify form-field submit-form" />
|
||||||
{% if form.password1.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password1.errors }}</p>{% endif %}
|
{% if form.password1.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password1.errors }}</p>{% endif %}
|
||||||
<input type="password"
|
<input type="password"
|
||||||
placeholder="Confirm password"
|
placeholder="Confirm password"
|
||||||
name="password2"
|
name="password2"
|
||||||
value="{{ form.password2.value|default:'' }}"
|
value="{{ form.password2.value|default:'' }}"
|
||||||
class="verify mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300" />
|
class="verify form-field submit-form" />
|
||||||
{% if form.password2.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password2.errors }}</p>{% endif %}
|
{% if form.password2.errors %}<p class="text-indianred-100 text-xs italic">{{ form.password2.errors }}</p>{% endif %}
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
id="submit-button"
|
id="submit-button"
|
||||||
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default">
|
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200" />Sign Up</button>
|
||||||
Sign Up
|
|
||||||
</button>
|
|
||||||
<p>
|
<p>
|
||||||
Have an account? Login <a class="underline text-skyblue-300" href="{% url 'login' %}">Here</a>!
|
Have an account? Login <a class="underline text-skyblue-300" href="{% url 'login' %}">Here</a>!
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -5,5 +5,7 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("signup/", views.signup_view, name="signup"),
|
path("signup/", views.signup_view, name="signup"),
|
||||||
path("login/", views.login_view, name="login"),
|
path("login/", views.login_view, name="login"),
|
||||||
path("<str:username>/", views.profile, name="profile"),
|
path("logout/", views.LogoutView.as_view(), name="logout"),
|
||||||
|
path("settings/", views.ProfileUpdateView.as_view(), name="settings"),
|
||||||
|
path("settings/security/", views.PasswordChangeView.as_view(), name="change_password"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,19 +1,82 @@
|
|||||||
from django.contrib.auth import authenticate, login
|
from django.contrib import messages
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth import authenticate, login, logout, update_session_auth_hash
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.contrib.auth.forms import PasswordChangeForm
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.views.generic import ListView, TemplateView, View
|
||||||
|
|
||||||
from .forms import SignUpForm
|
from fossdb.models import Project
|
||||||
from .models import User
|
|
||||||
|
from .forms import LoginForm, SignUpForm, UserChangeForm
|
||||||
|
|
||||||
|
|
||||||
def profile(request, username):
|
class ProfileUpdateView(LoginRequiredMixin, TemplateView):
|
||||||
user = get_object_or_404(User, username=username)
|
template_name = "setting.html"
|
||||||
|
login_url = "/login/"
|
||||||
|
redirect_field_name = "redirect_to"
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
user_form = UserChangeForm(instance=request.user)
|
||||||
|
context = {
|
||||||
|
"title": "Your profile",
|
||||||
|
"user_form": user_form,
|
||||||
|
}
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
user_form = UserChangeForm(request.POST, instance=request.user)
|
||||||
|
|
||||||
|
if user_form.is_valid():
|
||||||
|
user_form.save()
|
||||||
|
messages.add_message(request, messages.SUCCESS, "Your profile was successfully updated!")
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"title": user.username + ("" if not user.full_name else f" ({user.full_name})"),
|
"title": "Your profile",
|
||||||
"user": user,
|
"user_form": user_form,
|
||||||
}
|
}
|
||||||
return render(request, "profile.html", context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChangeView(LoginRequiredMixin, TemplateView):
|
||||||
|
template_name = "password.html"
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
form = PasswordChangeForm(user=request.user)
|
||||||
|
context = {
|
||||||
|
"title": "Change password",
|
||||||
|
"form": form,
|
||||||
|
}
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
form = PasswordChangeForm(data=request.POST, user=request.user)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
|
||||||
|
update_session_auth_hash(request, form.user)
|
||||||
|
messages.add_message(request, messages.SUCCESS, "Your password was successfully updated!")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"title": "Change password",
|
||||||
|
"form": form,
|
||||||
|
}
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileProjectListView(LoginRequiredMixin, ListView):
|
||||||
|
model = Project
|
||||||
|
template_name = "profile.html"
|
||||||
|
context_object_name = "projects"
|
||||||
|
login_url = "/login/"
|
||||||
|
redirect_field_name = "redirect_to"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Project.objects.filter(owner=self.request.user)
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
data["title"] = self.request.user.username + ("" if not self.request.user.full_name else f" ({self.request.user.full_name})")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def signup_view(request):
|
def signup_view(request):
|
||||||
@ -34,7 +97,7 @@ def signup_view(request):
|
|||||||
|
|
||||||
|
|
||||||
def login_view(request):
|
def login_view(request):
|
||||||
form = AuthenticationForm(data=request.POST or None)
|
form = LoginForm(data=request.POST or None)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.get_user()
|
user = form.get_user()
|
||||||
@ -46,3 +109,9 @@ def login_view(request):
|
|||||||
"form": form,
|
"form": form,
|
||||||
}
|
}
|
||||||
return render(request, "login.html", context)
|
return render(request, "login.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
class LogoutView(View):
|
||||||
|
def get(self, request):
|
||||||
|
logout(request)
|
||||||
|
return redirect("login")
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
import django_filters
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
from .models import Project
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserFilter(django_filters.FilterSet):
|
|
||||||
username = django_filters.CharFilter(lookup_expr="icontains")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ("username",)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectFilter(django_filters.FilterSet):
|
|
||||||
owner = UserFilter()
|
|
||||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
|
||||||
description = django_filters.CharFilter(lookup_expr="icontains")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Project
|
|
||||||
fields = (
|
|
||||||
"owner",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
)
|
|
||||||
@ -1,19 +1,33 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms import inlineformset_factory
|
||||||
|
|
||||||
from .models import HostingPlatform, ProgrammingLanguage, Project, ProjectHostingPlatform, ProjectProgrammingLanguage
|
from .models import HostingPlatform, License, OperatingSystemVersion, ProgrammingLanguage, Project, ProjectHostingPlatform, ProjectProgrammingLanguage, Tag
|
||||||
|
|
||||||
|
|
||||||
class HostingPlatformForm(forms.ModelForm):
|
class HostingPlatformForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProjectHostingPlatform
|
model = ProjectHostingPlatform
|
||||||
fields = (
|
fields = (
|
||||||
"url",
|
|
||||||
"hosting_platform",
|
"hosting_platform",
|
||||||
|
"url",
|
||||||
)
|
)
|
||||||
widgets = {
|
widgets = {
|
||||||
"hosting_platform": forms.Select(
|
"hosting_platform": forms.Select(
|
||||||
choices=HostingPlatform.objects.all(),
|
choices=HostingPlatform.objects.all(),
|
||||||
)
|
attrs={
|
||||||
|
"class": "form-field submit-form",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"url": forms.URLInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "url",
|
||||||
|
"class": "form-field submit-form font-roboto",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"hosting_platform": "",
|
||||||
|
"url": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -27,14 +41,32 @@ class ProgrammingLanguageForm(forms.ModelForm):
|
|||||||
widgets = {
|
widgets = {
|
||||||
"programming_language": forms.Select(
|
"programming_language": forms.Select(
|
||||||
choices=ProgrammingLanguage.objects.all(),
|
choices=ProgrammingLanguage.objects.all(),
|
||||||
|
attrs={
|
||||||
|
"class": "form-field submit-form",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
"percentage": forms.NumberInput(
|
"percentage": forms.NumberInput(
|
||||||
attrs={
|
attrs={
|
||||||
|
"placeholder": "Percentage",
|
||||||
|
"class": "form-field submit-form",
|
||||||
"min": "0",
|
"min": "0",
|
||||||
"max": "100",
|
"max": "100",
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
labels = {
|
||||||
|
"programming_language": "",
|
||||||
|
"percentage": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ProgrammingLanguageInlineFormSet = inlineformset_factory(
|
||||||
|
Project,
|
||||||
|
ProjectProgrammingLanguage,
|
||||||
|
form=ProgrammingLanguageForm,
|
||||||
|
extra=0,
|
||||||
|
can_delete=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProjectForm(forms.ModelForm):
|
class ProjectForm(forms.ModelForm):
|
||||||
@ -51,17 +83,39 @@ class ProjectForm(forms.ModelForm):
|
|||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(
|
"name": forms.TextInput(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
|
||||||
"placeholder": "Project name",
|
"placeholder": "Project name",
|
||||||
}
|
"class": "form-field submit-form font-roboto",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
"description": forms.Textarea(
|
"description": forms.Textarea(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
|
||||||
"placeholder": "Description",
|
"placeholder": "Description",
|
||||||
}
|
"class": "form-field submit-form font-roboto",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"license": forms.CheckboxSelectMultiple(
|
||||||
|
choices=License.objects.all(),
|
||||||
|
attrs={
|
||||||
|
"class": "checkbox-form",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"operating_system": forms.CheckboxSelectMultiple(
|
||||||
|
choices=OperatingSystemVersion.objects.all(),
|
||||||
|
attrs={
|
||||||
|
"class": "checkbox-form",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"tag": forms.CheckboxSelectMultiple(
|
||||||
|
choices=Tag.objects.all(),
|
||||||
|
attrs={
|
||||||
|
"class": "checkbox-form",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
"license": forms.CheckboxSelectMultiple(),
|
}
|
||||||
"tag": forms.CheckboxSelectMultiple(),
|
labels = {
|
||||||
"operating_system": forms.CheckboxSelectMultiple(),
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"license": "",
|
||||||
|
"tag": "",
|
||||||
|
"operating_system": "",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2023-06-29 14:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fossdb', '0005_operatingsystem_is_linux'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='projectprogramminglanguage',
|
||||||
|
name='percentage',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2023-06-29 15:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fossdb', '0006_alter_projectprogramminglanguage_percentage'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='license',
|
||||||
|
name='full_name',
|
||||||
|
field=models.CharField(db_index=True, max_length=100, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='license',
|
||||||
|
name='short_name',
|
||||||
|
field=models.CharField(db_index=True, max_length=50, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='operatingsystem',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(db_index=True, max_length=100, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='programminglanguage',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(db_index=True, max_length=100, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='project',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True, db_index=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='project',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(db_index=True, max_length=255),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
src/apps/fossdb/migrations/0008_alter_project_description.py
Normal file
18
src/apps/fossdb/migrations/0008_alter_project_description.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2023-06-29 15:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fossdb', '0007_alter_license_full_name_alter_license_short_name_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='project',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
]
|
||||||
16
src/apps/fossdb/migrations/0009_delete_star.py
Normal file
16
src/apps/fossdb/migrations/0009_delete_star.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Generated by Django 4.2.2 on 2023-06-29 16:51
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fossdb', '0008_alter_project_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Star',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -6,8 +6,8 @@ from django.db import models
|
|||||||
|
|
||||||
|
|
||||||
class License(models.Model):
|
class License(models.Model):
|
||||||
short_name = models.CharField(max_length=50, unique=True)
|
short_name = models.CharField(max_length=50, unique=True, db_index=True)
|
||||||
full_name = models.CharField(max_length=100, unique=True)
|
full_name = models.CharField(max_length=100, unique=True, db_index=True)
|
||||||
url = models.URLField(blank=True, default="")
|
url = models.URLField(blank=True, default="")
|
||||||
text = models.TextField(blank=True, default="")
|
text = models.TextField(blank=True, default="")
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ class License(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class OperatingSystem(models.Model):
|
class OperatingSystem(models.Model):
|
||||||
name = models.CharField(max_length=100, unique=True)
|
name = models.CharField(max_length=100, unique=True, db_index=True)
|
||||||
description = models.TextField(blank=True, default="")
|
description = models.TextField(blank=True, default="")
|
||||||
is_linux = models.BooleanField(blank=True, default=False)
|
is_linux = models.BooleanField(blank=True, default=False)
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ class Tag(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ProgrammingLanguage(models.Model):
|
class ProgrammingLanguage(models.Model):
|
||||||
name = models.CharField(max_length=100, unique=True)
|
name = models.CharField(max_length=100, unique=True, db_index=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -64,7 +64,7 @@ class ProgrammingLanguage(models.Model):
|
|||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="ID")
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="ID")
|
||||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255, db_index=True)
|
||||||
description = models.TextField(blank=True, default="")
|
description = models.TextField(blank=True, default="")
|
||||||
license = models.ManyToManyField(License, blank=True)
|
license = models.ManyToManyField(License, blank=True)
|
||||||
tag = models.ManyToManyField(Tag, blank=True)
|
tag = models.ManyToManyField(Tag, blank=True)
|
||||||
@ -72,10 +72,6 @@ class Project(models.Model):
|
|||||||
programming_language = models.ManyToManyField(ProgrammingLanguage, through="ProjectProgrammingLanguage", blank=True)
|
programming_language = models.ManyToManyField(ProgrammingLanguage, through="ProjectProgrammingLanguage", blank=True)
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
@property
|
|
||||||
def star_amount(self):
|
|
||||||
return self.star.count()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def runs_on_macos(self):
|
def runs_on_macos(self):
|
||||||
return self.operating_system.filter(operating_system__name="macOS").exists()
|
return self.operating_system.filter(operating_system__name="macOS").exists()
|
||||||
@ -117,7 +113,7 @@ class Project(models.Model):
|
|||||||
class ProjectProgrammingLanguage(models.Model):
|
class ProjectProgrammingLanguage(models.Model):
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||||
programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE)
|
programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE)
|
||||||
percentage = models.PositiveIntegerField(blank=True, null=True)
|
percentage = models.PositiveIntegerField(blank=True, default=0)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.project.owner}/{self.project.name} | {self.programming_language} | {self.percentage}%"
|
return f"{self.project.owner}/{self.project.name} | {self.programming_language} | {self.percentage}%"
|
||||||
@ -137,8 +133,3 @@ class ProjectHostingPlatform(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
|
|
||||||
class Star(models.Model):
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
|
||||||
|
|||||||
@ -3,32 +3,62 @@
|
|||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
{% block meta %}{% endblock %}
|
{% block meta %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post" id="project-form">
|
<form class="flex flex-col items-center justify-center space-y-4 my-auto font-condensed"
|
||||||
|
method="post"
|
||||||
|
id="project-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.name }}
|
||||||
{{ hosting_platform.management_form }}
|
{{ form.description }}
|
||||||
{{ hosting_platform.as_table }}
|
<div class="flex gap-4 justify-center">
|
||||||
<div id="language-formset">
|
<div class="">
|
||||||
{{ programming_language.management_form }}
|
<p class="text-xl font-abel text-center">Licenses</p>
|
||||||
{% for form in programming_language %}<div class="language-form">{{ form.as_table }}</div>{% endfor %}
|
{{ form.license }}
|
||||||
</div>
|
</div>
|
||||||
<!-- This button will trigger the JS to append another language form -->
|
<div>
|
||||||
<button type="button" id="add-more">+</button>
|
<p class="text-xl font-abel text-center">Tags</p>
|
||||||
<!-- Render the empty form, which you'll use as a template for new entries -->
|
{{ form.tag }}
|
||||||
<!-- Wrap it in a container so you can reference it by id and hide it -->
|
</div>
|
||||||
<div id="empty-form" style="display:none;">{{ empty_form.as_table }}</div>
|
<div>
|
||||||
<button type="submit">Submit</button>
|
<p class="text-xl font-abel text-center">Operating systems</p>
|
||||||
|
{{ form.operating_system }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ hosting_platform.management_form }}
|
||||||
|
<div class="flex gap-4 items-center">
|
||||||
|
<p class="text-xl font-abel">Hosting platform:</p>
|
||||||
|
{{ hosting_platform }}
|
||||||
|
</div>
|
||||||
|
{{ programming_languages.management_form }}
|
||||||
|
<!-- Languages container -->
|
||||||
|
<div class="" id="languages-container">
|
||||||
|
{% for language in programming_languages %}<div class="flex gap-4 items-center">{{ language }}</div>{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- Hidden empty form used as a template -->
|
||||||
|
<div id="empty-form" style="display:none">
|
||||||
|
<div class="flex gap-4 items-center">{{ programming_languages.empty_form }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="p-2 text-mediumaquamarine cursor-pointer hover:opacity-50 transition duration-100 ease-linear"
|
||||||
|
id="add-language">
|
||||||
|
<i class="fa-solid fa-plus fa-2xl"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button submit-button-enabled bg-mediumaquamarine text-gray-500"
|
||||||
|
ype="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script type="text/javascript">
|
||||||
document.querySelector("#add-more").addEventListener("click", function() {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
var formIndex = document.querySelector("#id_language-TOTAL_FORMS").value;
|
document.getElementById("add-language").addEventListener("click", () => {
|
||||||
var emptyFormDiv = document.querySelector("#empty-form");
|
var formIndex = document.getElementById("id_projectprogramminglanguage_set-TOTAL_FORMS");
|
||||||
var newFormHTML = emptyFormDiv.innerHTML.replace(/__prefix__/g, formIndex);
|
var emptyForm = document.getElementById("empty-form").innerHTML;
|
||||||
var newFormDiv = document.createElement("div");
|
|
||||||
newFormDiv.className = "language-form";
|
emptyForm = emptyForm.replace(/__prefix__/g, formIndex.value);
|
||||||
newFormDiv.innerHTML = newFormHTML;
|
|
||||||
document.querySelector("#language-formset").append(newFormDiv);
|
document.getElementById("languages-container")
|
||||||
document.querySelector("#id_language-TOTAL_FORMS").value = parseInt(formIndex) + 1;
|
.insertAdjacentHTML("beforeend", emptyForm);
|
||||||
});
|
|
||||||
|
formIndex.value = parseInt(formIndex.value) + 1;
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}Delete {{ project }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post"
|
<form method="post"
|
||||||
id="delete-form"
|
id="delete-form"
|
||||||
@ -9,9 +9,9 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="confirm-input"
|
id="confirm-input"
|
||||||
class="mt-1 block rounded-md border-2 border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-lightsteelblue-100 focus:border-cadetblue-300 text-center" />
|
class="text-center form-field border-slategray-200 hover:border-lightcoral bg-gray-300 focus:border-indianred-100 transition ease-linear" />
|
||||||
<button id="submit-button"
|
<button id="submit-button"
|
||||||
class="px-12 py-2 border border-transparent rounded-md text-lightsteelblue-100 font-bold uppercase bg-slategray-200 opacity-50 transform ease-in-out duration-500 cursor-default"
|
class="button submit-button-disabled text-lightsteelblue-100 bg-slategray-200 uppercase font-bold"
|
||||||
type="submit">Delete</button>
|
type="submit">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -23,30 +23,26 @@
|
|||||||
USER_INPUT.addEventListener("input", () => {
|
USER_INPUT.addEventListener("input", () => {
|
||||||
if (confirm_string == USER_INPUT.value) {
|
if (confirm_string == USER_INPUT.value) {
|
||||||
SUBMIT_BUTTON.classList.remove(
|
SUBMIT_BUTTON.classList.remove(
|
||||||
"bg-slategray-200",
|
"submit-button-disabled",
|
||||||
"text-lightsteelblue-100",
|
"text-lightsteelblue-100",
|
||||||
"opacity-50",
|
"bg-slategray-200"
|
||||||
"cursor-default"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.classList.add(
|
SUBMIT_BUTTON.classList.add(
|
||||||
"bg-indianred-100",
|
"submit-button-enabled",
|
||||||
"text-gray-500",
|
"text-gray-500",
|
||||||
"opacity-100",
|
"bg-indianred-100",
|
||||||
"hover:opacity-60"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.disabled = false
|
SUBMIT_BUTTON.disabled = false
|
||||||
} else {
|
} else {
|
||||||
SUBMIT_BUTTON.classList.remove(
|
SUBMIT_BUTTON.classList.remove(
|
||||||
"bg-indianred-100",
|
"submit-button-enabled",
|
||||||
"text-gray-500",
|
"text-gray-500",
|
||||||
"opacity-100",
|
"bg-indianred-100",
|
||||||
"hover:opacity-60"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.classList.add(
|
SUBMIT_BUTTON.classList.add(
|
||||||
"bg-slategray-200",
|
"submit-button-disabled",
|
||||||
"text-lightsteelblue-100",
|
"text-lightsteelblue-100",
|
||||||
"opacity-50",
|
"bg-slategray-200"
|
||||||
"cursor-default"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.disabled = true
|
SUBMIT_BUTTON.disabled = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,45 +13,73 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<!-- os platform icons -->
|
<!-- os platform icons -->
|
||||||
<div class="my-4 flex justify-center items-center gap-x-4">
|
<div class="my-4 flex justify-center items-center gap-x-4">
|
||||||
<a href=""><i class="fa-brands fa-linux fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
<!-- linux -->
|
||||||
|
<a href="/search/?q=linux">
|
||||||
|
<span title="Linux"
|
||||||
|
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||||
{% if project.runs_on_linux %}
|
{% if project.runs_on_linux %}
|
||||||
text-lightsteelblue-100
|
text-lightsteelblue-100
|
||||||
{% else %}
|
{% else %}
|
||||||
text-slategray-200
|
text-slategray-200
|
||||||
{% endif %}"></i></a>
|
{% endif %}">
|
||||||
<a href=""><i class="fa-brands fa-windows fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
<i class="fa-brands fa-linux fa-lg"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<!-- windows -->
|
||||||
|
<a href="/search/?q=windows">
|
||||||
|
<span title="Windows"
|
||||||
|
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||||
{% if project.runs_on_windows %}
|
{% if project.runs_on_windows %}
|
||||||
text-lightsteelblue-100
|
text-lightsteelblue-100
|
||||||
{% else %}
|
{% else %}
|
||||||
text-slategray-200
|
text-slategray-200
|
||||||
{% endif %}"></i></a>
|
{% endif %}">
|
||||||
<a href=""><i class="fa-brands fa-apple fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
<i class="fa-brands fa-windows fa-lg"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<!-- macos -->
|
||||||
|
<a href="/search/?q=macos">
|
||||||
|
<span title="macOS"
|
||||||
|
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||||
{% if project.runs_on_macos %}
|
{% if project.runs_on_macos %}
|
||||||
text-lightsteelblue-100
|
text-lightsteelblue-100
|
||||||
{% else %}
|
{% else %}
|
||||||
text-slategray-200
|
text-slategray-200
|
||||||
{% endif %}"></i></a>
|
{% endif %}">
|
||||||
<a href=""><i id="ios"
|
<i class="fa-brands fa-apple fa-lg"></i>
|
||||||
class="fa-brands fa-app-store-ios fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
</span>
|
||||||
|
</a>
|
||||||
|
<!-- ios -->
|
||||||
|
<a href="/search/?q=ios">
|
||||||
|
<span title="iOS"
|
||||||
|
class="fa-lg hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||||
{% if project.runs_on_ios %}
|
{% if project.runs_on_ios %}
|
||||||
text-lightsteelblue-100
|
text-lightsteelblue-100
|
||||||
{% else %}
|
{% else %}
|
||||||
text-slategray-200
|
text-slategray-200
|
||||||
{% endif %}"></i></a>
|
{% endif %}">
|
||||||
<a href=""><i id="android"
|
<i class="fa-brands fa-app-store-ios"></i>
|
||||||
class="fa-brands fa-android fa-lg hover:text-lightsteelblue-200 transform duration-300 ease-in-out
|
</span>
|
||||||
|
</a>
|
||||||
|
<!-- android -->
|
||||||
|
<a href="/search/?q=android">
|
||||||
|
<span title="Android"
|
||||||
|
class="hover:text-lightsteelblue-200 transform duration-200 ease-linear
|
||||||
{% if project.runs_on_android %}
|
{% if project.runs_on_android %}
|
||||||
text-lightsteelblue-100
|
text-lightsteelblue-100
|
||||||
{% else %}
|
{% else %}
|
||||||
text-slategray-200
|
text-slategray-200
|
||||||
{% endif %}"></i></a>
|
{% endif %}">
|
||||||
|
<i class="fa-brands fa-android fa-lg"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- tags -->
|
<!-- tags -->
|
||||||
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
|
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
|
||||||
{% for tag in project.tag.all|dictsort:"name" %}
|
{% for tag in project.tag.all|dictsort:"name" %}
|
||||||
<a href="">
|
<a href="/search/?q={{ tag.name }}">
|
||||||
<span title="{{ tag.description }}"
|
<span title="{{ tag.description }}"
|
||||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-300 ease-in-out">
|
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@ -60,9 +88,9 @@
|
|||||||
<!-- programming languages -->
|
<!-- programming languages -->
|
||||||
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
|
<div class="my-8 flex flex-wrap justify-center items-start gap-2">
|
||||||
{% for language in project.projectprogramminglanguage_set.all|dictsortreversed:"percentage" %}
|
{% for language in project.projectprogramminglanguage_set.all|dictsortreversed:"percentage" %}
|
||||||
<a href="">
|
<a href="/search/?q={{ language.name }}">
|
||||||
<span title="{{ language.percentage }}%"
|
<span title="{{ language.percentage }}%"
|
||||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-300 ease-in-out">
|
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
|
||||||
{{ language.programming_language }}
|
{{ language.programming_language }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@ -75,7 +103,7 @@
|
|||||||
<div class="flex justify-between w-full mt-4 font-abel">
|
<div class="flex justify-between w-full mt-4 font-abel">
|
||||||
<!-- licenses -->
|
<!-- licenses -->
|
||||||
<div>
|
<div>
|
||||||
<button class="text-xl underline font-abel hover:text-lightsteelblue-200 transform duration-300 ease-in-out"
|
<button class="text-xl underline font-abel hover:text-lightsteelblue-200 transform duration-200 ease-linear"
|
||||||
id="menu-button"
|
id="menu-button"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="true">Licenses</button>
|
aria-expanded="true">Licenses</button>
|
||||||
@ -83,7 +111,7 @@
|
|||||||
<!-- hosting platform -->
|
<!-- hosting platform -->
|
||||||
<div class="">
|
<div class="">
|
||||||
<p>
|
<p>
|
||||||
See project source code and read more <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-300 ease-in-out"
|
See project source code and read more <a class="underline text-skyblue-300 hover:text-cadetblue-300 transform duration-200 ease-linear"
|
||||||
href="{{ project.projecthostingplatform.url }}"
|
href="{{ project.projecthostingplatform.url }}"
|
||||||
target="_blank">here</a>!
|
target="_blank">here</a>!
|
||||||
</p>
|
</p>
|
||||||
@ -100,7 +128,7 @@
|
|||||||
{% for license in project.license.all|dictsort:"short_name" %}
|
{% for license in project.license.all|dictsort:"short_name" %}
|
||||||
<a href="{{ license.url }}">
|
<a href="{{ license.url }}">
|
||||||
<span title="{{ license.full_name }}"
|
<span title="{{ license.full_name }}"
|
||||||
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60">
|
class="bg-opacity-0 border rounded-xl border-slategray-200 px-3 text-xs min-w-16 hover:bg-steelblue-400 hover:bg-opacity-60 transform duration-200 ease-linear">
|
||||||
{{ license }}
|
{{ license }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@ -111,12 +139,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if user == project.owner %}
|
{% if user == project.owner %}
|
||||||
<div class="flex justify-between mt-8 mx-16">
|
<div class="flex justify-between mt-8 mx-16">
|
||||||
<button class="px-12 py-2 border border-transparent rounded-md text-gray-500 bg-skyblue-300 hover:opacity-60 transform ease-in-out duration-500">
|
<a href="{% url 'project-update' project.owner project.name %}">
|
||||||
<a href="{% url 'project-update' project.owner project.name %}">Update</a>
|
<button class="button hover:bg-opacity-60 bg-mediumpurple-100 text-gray-500">Update</button>
|
||||||
</button>
|
</a>
|
||||||
<button class="px-12 py-2 border border-transparent rounded-md text-gray-500 bg-burlywood hover:opacity-60 transform ease-in-out duration-500">
|
<a href="{% url 'project-delete' project.owner project.name %}">
|
||||||
<a href="{% url 'project-delete' project.owner project.name %}">Delete</a>
|
<button class="button hover:bg-opacity-60 bg-lightcoral text-gray-500">Delete</button>
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,13 +3,20 @@
|
|||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
{% block meta %}{% endblock %}
|
{% block meta %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="py-8 px-16">
|
||||||
|
<h1 class="font-abel text-4xl mb-8">Projects</h1>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{% for project in projects %}
|
{% for project in projects %}
|
||||||
<div>
|
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
|
||||||
<a href="{{ project.get_absolute_url }}">{{ project }}</a>
|
<a href="{{ project.get_absolute_url }}"
|
||||||
|
class="hover:text-steelblue-100 transition duration-300 ease-linear">
|
||||||
|
<h2 class="font-abel text-2xl">{{ project.name }}</h2>
|
||||||
|
</a>
|
||||||
|
<p class="max-w-xs text-justify">{{ project.description|slice:500 }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ project.description }}</div>
|
|
||||||
<hr />
|
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p>No projects found</p>
|
<p class="text-center font-abel text-xl">No projects yet.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
22
src/apps/fossdb/templates/search.html
Normal file
22
src/apps/fossdb/templates/search.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block meta %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="py-8 px-32">
|
||||||
|
<h1 class="font-abel text-4xl mb-8">Search Results</h1>
|
||||||
|
<div class="grid grid-cols-1 gap-4">
|
||||||
|
{% for project in projects %}
|
||||||
|
<div class="border border-steelblue-100 p-8 flex flex-row gap-4 justify-between">
|
||||||
|
<a href="{{ project.get_absolute_url }}"
|
||||||
|
class="hover:text-steelblue-100 transition duration-300 ease-linear">
|
||||||
|
<h2 class="font-abel text-3xl">{{ project.name }}</h2>
|
||||||
|
</a>
|
||||||
|
<p class="max-w-2xl text-justify">{{ project.description|slice:500 }}</p>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-center font-abel text-xl">No projects yet.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -3,9 +3,7 @@ from django.urls import path
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("explore/", views.ProjectListView.as_view(), name="explore"),
|
path("", views.ProjectDetailView.as_view(), name="project-detail"),
|
||||||
path("contribute/", views.ProjectCreateView.as_view(), name="contribute"),
|
path("edit/", views.ProjectUpdateView.as_view(), name="project-update"),
|
||||||
path("<str:owner>/<str:project_name>/", views.ProjectDetailView.as_view(), name="project-detail"),
|
path("delete/", views.ProjectDeleteView.as_view(), name="project-delete"),
|
||||||
path("<str:owner>/<str:project_name>/edit/", views.ProjectUpdateView.as_view(), name="project-update"),
|
|
||||||
path("<str:owner>/<str:project_name>/delete/", views.ProjectDeleteView.as_view(), name="project-delete"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,64 +1,84 @@
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||||
from django.forms import inlineformset_factory
|
from django.db.models import Q
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.views.generic import CreateView, DeleteView, DetailView, UpdateView
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from django_filters.views import FilterView
|
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
|
||||||
|
|
||||||
from .filters import ProjectFilter
|
from .forms import HostingPlatformForm, ProgrammingLanguageInlineFormSet, ProjectForm
|
||||||
|
from .models import Project
|
||||||
from .forms import HostingPlatformForm, ProgrammingLanguageForm, ProjectForm
|
|
||||||
from .models import Project, ProjectProgrammingLanguage
|
|
||||||
|
|
||||||
ProgrammingLanguageInlineFormset = inlineformset_factory(
|
|
||||||
Project,
|
|
||||||
ProjectProgrammingLanguage,
|
|
||||||
form=ProgrammingLanguageForm,
|
|
||||||
extra=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectListView(FilterView):
|
class SearchResultsListView(ListView):
|
||||||
|
model = Project
|
||||||
|
template_name = "search.html"
|
||||||
|
context_object_name = "projects"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
query = self.request.GET.get("q")
|
||||||
|
return Project.objects.filter(
|
||||||
|
Q(owner__username__icontains=query)
|
||||||
|
| Q(name__icontains=query)
|
||||||
|
# | Q(description__icontains=query)
|
||||||
|
| Q(license__short_name__icontains=query)
|
||||||
|
| Q(license__full_name__icontains=query)
|
||||||
|
| Q(tag__name__icontains=query)
|
||||||
|
| Q(operating_system__operating_system__name__icontains=query)
|
||||||
|
| Q(operating_system__codename__icontains=query)
|
||||||
|
| Q(programming_language__name__icontains=query)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
data["title"] = "FOSSDB | Search"
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectListView(ListView):
|
||||||
model = Project
|
model = Project
|
||||||
template_name = "explore.html"
|
template_name = "explore.html"
|
||||||
filterset_class = ProjectFilter
|
|
||||||
context_object_name = "projects"
|
context_object_name = "projects"
|
||||||
paginate_by = 100 # optional 10 projects a page
|
paginate_by = 50 # amount of items on screen
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
data["title"] = "FOSSDB | Explore"
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ProjectCreateView(LoginRequiredMixin, CreateView):
|
class ProjectCreateView(LoginRequiredMixin, CreateView):
|
||||||
model = Project
|
model = Project
|
||||||
form_class = ProjectForm
|
form_class = ProjectForm
|
||||||
template_name = "create_view.html"
|
template_name = "create_view.html"
|
||||||
login_url = "/login/"
|
login_url = reverse_lazy("login")
|
||||||
redirect_field_name = "redirect_to"
|
redirect_field_name = "redirect_to"
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
data = super().get_context_data(**kwargs)
|
data = super().get_context_data(**kwargs)
|
||||||
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, prefix="hosting")
|
data["title"] = "FOSSDB | Create Project"
|
||||||
data["programming_language"] = ProgrammingLanguageInlineFormset(self.request.POST or None, prefix="language")
|
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None)
|
||||||
data["empty_form"] = ProgrammingLanguageInlineFormset(prefix="language_empty")
|
data["programming_languages"] = ProgrammingLanguageInlineFormSet(self.request.POST or None)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
context = self.get_context_data()
|
context = self.get_context_data()
|
||||||
|
|
||||||
form.instance.owner = self.request.user
|
form.instance.owner = self.request.user
|
||||||
|
|
||||||
|
self.object = form.save() # Save project form
|
||||||
|
|
||||||
hosting_platform = context["hosting_platform"]
|
hosting_platform = context["hosting_platform"]
|
||||||
programming_language = context["programming_language"]
|
|
||||||
self.object = form.save()
|
|
||||||
if hosting_platform.is_valid():
|
if hosting_platform.is_valid():
|
||||||
hosting_platform.instance.project = self.object
|
hosting_platform = hosting_platform.save(commit=False)
|
||||||
|
hosting_platform.project = self.object
|
||||||
hosting_platform.save()
|
hosting_platform.save()
|
||||||
# TODO: allow adding multiple languages
|
|
||||||
if programming_language.is_valid():
|
programming_languages = context["programming_languages"]
|
||||||
for instance in programming_language.save(commit=False):
|
if programming_languages.is_valid():
|
||||||
instance.project = self.object
|
programming_languages.instance = self.object
|
||||||
instance.save()
|
programming_languages.save()
|
||||||
programming_language.save_m2m()
|
|
||||||
if hosting_platform.is_valid() and programming_language.is_valid():
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
else:
|
|
||||||
return self.render_to_response(self.get_context_data(form=form))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetailView(DetailView):
|
class ProjectDetailView(DetailView):
|
||||||
@ -75,7 +95,7 @@ class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
|||||||
form_class = ProjectForm
|
form_class = ProjectForm
|
||||||
slug_field = "name"
|
slug_field = "name"
|
||||||
slug_url_kwarg = "project_name"
|
slug_url_kwarg = "project_name"
|
||||||
login_url = "/login/"
|
login_url = reverse_lazy("login")
|
||||||
redirect_field_name = "redirect_to"
|
redirect_field_name = "redirect_to"
|
||||||
|
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
@ -85,24 +105,48 @@ class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
|||||||
return redirect("login")
|
return redirect("login")
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
data = super(ProjectUpdateView, self).get_context_data(**kwargs)
|
data = super().get_context_data(**kwargs)
|
||||||
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, instance=self.object.projecthostingplatform, prefix="hosting")
|
data["title"] = f"Edit {self.object}"
|
||||||
data["programming_language"] = ProgrammingLanguageInlineFormset(self.request.POST or None, instance=self.object, prefix="language")
|
data["hosting_platform"] = HostingPlatformForm(self.request.POST or None, instance=self.object.projecthostingplatform)
|
||||||
data["empty_form"] = ProgrammingLanguageInlineFormset(prefix="language_empty")
|
data["programming_languages"] = ProgrammingLanguageInlineFormSet(self.request.POST or None, instance=self.object)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
context = self.get_context_data()
|
||||||
|
|
||||||
|
form.instance.owner = self.request.user
|
||||||
|
|
||||||
|
self.object = form.save() # Save project form
|
||||||
|
|
||||||
|
hosting_platform = context["hosting_platform"]
|
||||||
|
if hosting_platform.is_valid():
|
||||||
|
hosting_platform.project = self.object
|
||||||
|
hosting_platform.save()
|
||||||
|
|
||||||
|
programming_languages = context["programming_languages"]
|
||||||
|
if programming_languages.is_valid():
|
||||||
|
programming_languages.instance = self.object
|
||||||
|
programming_languages.save()
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class ProjectDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
class ProjectDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
||||||
model = Project
|
model = Project
|
||||||
template_name = "delete_view.html"
|
template_name = "delete_view.html"
|
||||||
slug_field = "name"
|
slug_field = "name"
|
||||||
slug_url_kwarg = "project_name"
|
slug_url_kwarg = "project_name"
|
||||||
login_url = "/login/"
|
login_url = reverse_lazy("login")
|
||||||
redirect_field_name = "redirect_to"
|
redirect_field_name = "redirect_to"
|
||||||
success_url = "/"
|
success_url = reverse_lazy("explore")
|
||||||
|
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
return self.get_object().owner == self.request.user
|
return self.get_object().owner == self.request.user
|
||||||
|
|
||||||
def handle_no_permission(self):
|
def handle_no_permission(self):
|
||||||
return redirect("login")
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
data["title"] = f"Delete {self.object}"
|
||||||
|
return data
|
||||||
|
|||||||
5
src/apps/main/templates/dashboard.html
Normal file
5
src/apps/main/templates/dashboard.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block meta %}{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
5
src/apps/main/templates/help.html
Normal file
5
src/apps/main/templates/help.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block meta %}{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
5
src/apps/main/templates/news.html
Normal file
5
src/apps/main/templates/news.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block meta %}{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
@ -1,11 +1,20 @@
|
|||||||
|
from account.views import ProfileProjectListView
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from fossdb.views import ProjectCreateView, ProjectListView, SearchResultsListView
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.homepage, name="homepage"),
|
path("", views.homepage, name="homepage"),
|
||||||
path("contribute/", views.contribute, name="contribute"),
|
path("search/", SearchResultsListView.as_view(), name="search"),
|
||||||
path("news/", views.news, name="news"),
|
path("explore/", ProjectListView.as_view(), name="explore"),
|
||||||
|
path("contribute/", ProjectCreateView.as_view(), name="contribute"),
|
||||||
path("dashboard/", views.dashboard, name="dashboard"),
|
path("dashboard/", views.dashboard, name="dashboard"),
|
||||||
|
path("news/", views.news, name="news"),
|
||||||
path("help/", views.help, name="help"),
|
path("help/", views.help, name="help"),
|
||||||
|
path("login/", views.login),
|
||||||
|
path("logout/", views.logout),
|
||||||
|
path("signup/", views.signup),
|
||||||
|
path("<str:username>/", ProfileProjectListView.as_view(), name="profile"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,10 +1,22 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
|
|
||||||
def homepage(request):
|
def homepage(request):
|
||||||
return render(request, "homepage.html", {"title": "FOSSDB"})
|
return render(request, "homepage.html", {"title": "FOSSDB"})
|
||||||
|
|
||||||
|
|
||||||
|
def login(request):
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
|
||||||
|
def logout(request):
|
||||||
|
return redirect("logout")
|
||||||
|
|
||||||
|
|
||||||
|
def signup(request):
|
||||||
|
return redirect("signup")
|
||||||
|
|
||||||
|
|
||||||
def contribute(request):
|
def contribute(request):
|
||||||
return render(request, "contribute.html", {"title": "FOSSDB | Contribute"})
|
return render(request, "contribute.html", {"title": "FOSSDB | Contribute"})
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,6 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Abel&display=swap");
|
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Rationale&display=swap");
|
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap");
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply leading-[normal] m-0;
|
@apply leading-[normal] m-0;
|
||||||
}
|
}
|
||||||
@ -28,3 +24,27 @@ body {
|
|||||||
background: linear-gradient(to right, transparent, #27a1b9, transparent);
|
background: linear-gradient(to right, transparent, #27a1b9, transparent);
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
@apply mt-1 block rounded-md border-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-form {
|
||||||
|
@apply border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-slategray-100 focus:border-cadetblue-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-form {
|
||||||
|
@apply border-slategray-200 hover:border-lightsteelblue-100 bg-gray-300 placeholder-slategray-100 focus:border-cadetblue-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
@apply font-condensed text-xl px-12 py-2 border rounded-md transform ease-linear duration-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button-disabled {
|
||||||
|
@apply opacity-60 cursor-default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button-enabled {
|
||||||
|
@apply opacity-100 hover:opacity-60 hover:bg-opacity-60;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,11 +7,11 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abel&family=Rationale&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"
|
||||||
rel="stylesheet" />
|
rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Abel&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
<link href="https://fonts.googleapis.com/css2?family=Abel&display=swap"
|
||||||
rel="stylesheet" />
|
rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
<link href="https://fonts.googleapis.com/css2?family=Rationale&display=swap"
|
||||||
rel="stylesheet" />
|
rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||||
rel="stylesheet" />
|
rel="stylesheet" />
|
||||||
@ -27,32 +27,35 @@
|
|||||||
<header class="flex justify-between items-center px-6 py-2 font-abel text-xl">
|
<header class="flex justify-between items-center px-6 py-2 font-abel text-xl">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<!-- logo -->
|
<!-- logo -->
|
||||||
<div class="flex items-center text-4xl font-abel">
|
<div class="flex items-center text-4xl font-rationale">
|
||||||
<img class=""
|
<img class=""
|
||||||
width="40"
|
width="40"
|
||||||
src="{% static 'img/icons/logo.svg' %}"
|
src="{% static 'img/icons/logo.svg' %}"
|
||||||
alt="logo" />
|
alt="logo" />
|
||||||
<a class="hover:text-skyblue-300 transform duration-300 ease-in-out"
|
<a class="hover:text-skyblue-300 transform duration-300 ease-linear"
|
||||||
href="{% url 'homepage' %}">foss<span class="text-skyblue-300">db</span></a>
|
href="{% url 'homepage' %}">foss<span class="text-skyblue-300">db</span></a>
|
||||||
</div>
|
</div>
|
||||||
<!-- search -->
|
<!-- search -->
|
||||||
<div class="relative items-center flex">
|
<form action="{% url 'search' %}"
|
||||||
|
method="get"
|
||||||
|
class="relative items-center flex">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
|
name="q"
|
||||||
placeholder="Search projects..."
|
placeholder="Search projects..."
|
||||||
class="py-2 mx-4 placeholder-slategray-100 bg-gray-300 border-0 border-b-2 border-b-slategray-100 hover:border-b-lightsteelblue-100 transform duration-200 ease-in-out focus:outline-none focus:border-b-skyblue-300 focus:border-b-2 max-w-[10rem]" />
|
class="py-2 mx-4 placeholder-slategray-100 bg-gray-300 border-0 border-b-2 border-b-slategray-100 hover:border-b-lightsteelblue-100 transform duration-200 ease-linear focus:outline-none focus:border-b-skyblue-300 focus:border-b-2 max-w-[10rem]" />
|
||||||
</div>
|
</form>
|
||||||
<!-- navbar -->
|
<!-- navbar -->
|
||||||
<nav class="uppercase flex gap-x-6 items-center">
|
<nav class="uppercase flex gap-x-6 items-center">
|
||||||
<a href="{% url 'explore' %}"
|
<a href="{% url 'explore' %}"
|
||||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">explore</a>
|
class="hover:text-skyblue-300 transform duration-200 ease-linear">explore</a>
|
||||||
<a href="{% url 'contribute' %}"
|
<a href="{% url 'contribute' %}"
|
||||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">contribute</a>
|
class="hover:text-skyblue-300 transform duration-200 ease-linear">contribute</a>
|
||||||
<a href="{% url 'news' %}"
|
<a href="{% url 'news' %}"
|
||||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">news</a>
|
class="hover:text-skyblue-300 transform duration-200 ease-linear">news</a>
|
||||||
<a href="{% url 'dashboard' %}"
|
<a href="{% url 'dashboard' %}"
|
||||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">dashboard</a>
|
class="hover:text-skyblue-300 transform duration-200 ease-linear">dashboard</a>
|
||||||
<a href="{% url 'help' %}"
|
<a href="{% url 'help' %}"
|
||||||
class="hover:text-skyblue-300 transform duration-200 ease-in-out">help</a>
|
class="hover:text-skyblue-300 transform duration-200 ease-linear">help</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
@ -61,14 +64,12 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<p class="mx-4">{{ user.username }}</p>
|
<p class="mx-4">{{ user.username }}</p>
|
||||||
<a href="{% url 'profile' user.username %}">
|
<a href="{% url 'profile' user.username %}">
|
||||||
<img src="{{ user.profile.picture.url }}"
|
<img src="{{ user.profile.picture.url }}" class="w-[2rem]" alt="pic" />
|
||||||
class="w-[2rem]"
|
|
||||||
alt="{{ user.username }}'s profile picture" />
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'login' %}"
|
<a href="{% url 'login' %}"
|
||||||
class="hover:text-skyblue-300 transition duration-300 ease-in-out"><i class="fa-solid fa-user fa-lg ml-4 "></i></a>
|
class="hover:text-skyblue-300 transition duration-300 ease-linear"><i class="fa-solid fa-user fa-lg ml-4 "></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -81,10 +82,9 @@
|
|||||||
<p>
|
<p>
|
||||||
FOSSDB is a passion project of <a class="underline"
|
FOSSDB is a passion project of <a class="underline"
|
||||||
href="https://github.com/kristoferssolo"
|
href="https://github.com/kristoferssolo"
|
||||||
target="_blank">@kristoferssolo</a> and a dedicated community of reporters.
|
target="_blank">@kristoferssolo</a>.
|
||||||
</p>
|
</p>
|
||||||
<!-- TODO: finish these sentences -->
|
<!-- TODO: finish these sentences -->
|
||||||
<p>This site uses data from GitHub as well as data...</p>
|
|
||||||
<p>This site has no affiliation with GitHub or any other hosting platform.</p>
|
<p>This site has no affiliation with GitHub or any other hosting platform.</p>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@ -1,3 +1,11 @@
|
|||||||
|
Array.from(document.getElementsByClassName("remove-btn")).forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
console.log("TRUE")
|
||||||
|
const hiddenDeleteInput = button.previousElementSibling
|
||||||
|
hiddenDeleteInput.value = "on"
|
||||||
|
button.parentElement.style.display = "none"
|
||||||
|
})
|
||||||
|
})
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
const FORM_VERIFY = document.getElementsByClassName("verify")
|
const FORM_VERIFY = document.getElementsByClassName("verify")
|
||||||
const SUBMIT_BUTTON = document.getElementById("submit-button")
|
const SUBMIT_BUTTON = document.getElementById("submit-button")
|
||||||
@ -10,30 +18,26 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
)
|
)
|
||||||
if (ALL_FILLED) {
|
if (ALL_FILLED) {
|
||||||
SUBMIT_BUTTON.classList.remove(
|
SUBMIT_BUTTON.classList.remove(
|
||||||
"bg-slategray-200",
|
"submit-button-disabled",
|
||||||
"text-lightsteelblue-100",
|
"text-lightsteelblue-100",
|
||||||
"opacity-50",
|
"bg-slategray-200"
|
||||||
"cursor-default"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.classList.add(
|
SUBMIT_BUTTON.classList.add(
|
||||||
"bg-skyblue-300",
|
"submit-button-enabled",
|
||||||
"text-gray-500",
|
"text-gray-500",
|
||||||
"opacity-100",
|
"bg-skyblue-300"
|
||||||
"hover:opacity-60"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.disabled = false
|
SUBMIT_BUTTON.disabled = false
|
||||||
} else {
|
} else {
|
||||||
SUBMIT_BUTTON.classList.remove(
|
SUBMIT_BUTTON.classList.remove(
|
||||||
"bg-skyblue-300",
|
"submit-button-enabled",
|
||||||
"text-gray-500",
|
"text-gray-500",
|
||||||
"opacity-100",
|
"bg-skyblue-300"
|
||||||
"hover:opacity-60"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.classList.add(
|
SUBMIT_BUTTON.classList.add(
|
||||||
"bg-slategray-200",
|
"submit-button-disabled",
|
||||||
"text-lightsteelblue-100",
|
"text-lightsteelblue-100",
|
||||||
"opacity-50",
|
"bg-slategray-200"
|
||||||
"cursor-default"
|
|
||||||
)
|
)
|
||||||
SUBMIT_BUTTON.disabled = true
|
SUBMIT_BUTTON.disabled = true
|
||||||
}
|
}
|
||||||
|
|||||||
24
tox.ini
24
tox.ini
@ -1,24 +0,0 @@
|
|||||||
[tox]
|
|
||||||
minversion = 3.8.0
|
|
||||||
envlist = django, flake8, mypy
|
|
||||||
isolated_build = true
|
|
||||||
|
|
||||||
[gh-actions]
|
|
||||||
python =
|
|
||||||
3.10: py310, mypy, flake8
|
|
||||||
|
|
||||||
[testenv:django]
|
|
||||||
basepython = python3.10
|
|
||||||
deps = django
|
|
||||||
commands = python manage.py test
|
|
||||||
|
|
||||||
[testenv:flake8]
|
|
||||||
basepython = python3.10
|
|
||||||
deps = flake8
|
|
||||||
commands = flake8 FOSSDB_web
|
|
||||||
|
|
||||||
[testenv:mypy]
|
|
||||||
basepython = python3.10
|
|
||||||
deps =
|
|
||||||
-r{toxinidir}/requirements_dev.txt
|
|
||||||
commands = mypy FOSSDB_web
|
|
||||||
Loading…
Reference in New Issue
Block a user