diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..df0053630 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*__pycache__* +.env diff --git a/db.sqlite3 b/db.sqlite3 index 1cd07fd88..c919a2d48 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/home/admin.py b/home/admin.py index 5a4e10383..e7b6e6a33 100644 --- a/home/admin.py +++ b/home/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django import forms -from home.models import Blog +from home.models import Blog, Project, Skill, About, Category # Register your models here. class BlogAdminForm(forms.ModelForm): @@ -13,4 +13,8 @@ class Meta: class BlogAdmin(admin.ModelAdmin): form = BlogAdminForm -admin.site.register(Blog, BlogAdmin) \ No newline at end of file +admin.site.register(Blog, BlogAdmin) +admin.site.register(Project) +admin.site.register(Skill) +admin.site.register(About) +admin.site.register(Category) diff --git a/home/migrations/0001_initial.py b/home/migrations/0001_initial.py deleted file mode 100644 index f8ea8f939..000000000 --- a/home/migrations/0001_initial.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 4.1.4 on 2023-01-22 06:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Blog', - fields=[ - ('sno', models.AutoField(primary_key=True, serialize=False)), - ('title', models.CharField(max_length=200)), - ('meta', models.CharField(max_length=300)), - ('content', models.TextField()), - ('thumbnail_img', models.ImageField(blank=True, null=True, upload_to='images/')), - ('slug', models.CharField(max_length=100)), - ('time', models.DateField(auto_now_add=True)), - ], - ), - ] diff --git a/home/migrations/0002_blog_category.py b/home/migrations/0002_blog_category.py deleted file mode 100644 index 241a11826..000000000 --- a/home/migrations/0002_blog_category.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1.4 on 2023-01-25 12:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('home', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='blog', - name='category', - field=models.CharField(default='uncategorized', max_length=255), - ), - ] diff --git a/home/migrations/0003_blog_remark.py b/home/migrations/0003_blog_remark.py deleted file mode 100644 index 949e111aa..000000000 --- a/home/migrations/0003_blog_remark.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.1.4 on 2023-01-26 07:00 - -import ckeditor.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('home', '0002_blog_category'), - ] - - operations = [ - migrations.AddField( - model_name='blog', - name='remark', - field=ckeditor.fields.RichTextField(null=True, unique=True), - ), - ] diff --git a/home/migrations/0004_remove_blog_remark.py b/home/migrations/0004_remove_blog_remark.py deleted file mode 100644 index f45a166de..000000000 --- a/home/migrations/0004_remove_blog_remark.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.1.4 on 2023-01-26 07:12 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('home', '0003_blog_remark'), - ] - - operations = [ - migrations.RemoveField( - model_name='blog', - name='remark', - ), - ] diff --git a/home/migrations/0005_blog_thumbnail_url.py b/home/migrations/0005_blog_thumbnail_url.py deleted file mode 100644 index 56bcf0a19..000000000 --- a/home/migrations/0005_blog_thumbnail_url.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1.4 on 2023-01-31 05:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('home', '0004_remove_blog_remark'), - ] - - operations = [ - migrations.AddField( - model_name='blog', - name='thumbnail_url', - field=models.URLField(blank=True, null=True), - ), - ] diff --git a/home/models.py b/home/models.py index aa1847cca..a0438f720 100644 --- a/home/models.py +++ b/home/models.py @@ -1,6 +1,59 @@ +from typing import Iterable from django.db import models +from django.utils import timezone +import random + +ALPHA = "abcdefghijklmnopqrstuvwxyz " # Create your models here. +class Project(models.Model): + title = models.CharField(max_length=200) + description = models.CharField(max_length=300) + features = models.TextField(null=True, blank=True) + thumbnail_url = models.URLField(blank=True, null=True) + technologies = models.CharField(max_length=255) + demo_url = models.URLField(blank=True, null=True) + github_url = models.URLField(blank=True, null=True) + date = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.title + +class Skill(models.Model): + name = models.CharField(max_length=200) + rate = models.IntegerField(verbose_name="Rate (out of 100)", default=0, null=False, blank=0) + tag = models.CharField(max_length=200, editable=False, default="", null=False) + + def clamp_rate(self): + self.rate = min(100, max(0, self.rate)) + + def save(self, *args) -> None: + self.tag = "".join([char.lower() for char in self.tag if char.lower() in ALPHA]) + + if self.tag == "": + for _ in range(15): + self.tag += random.choice(ALPHA) + + self.tag = self.tag.replace(" ", "_") + + self.clamp_rate() + return super().save(*args) + + def __str__(self): + return self.name + +class About(models.Model): + text = models.TextField(blank=True, null=True) + + def __str__(self): + return self.text.split("\n")[0][:20] # first line until 2Oth char + +class Category(models.Model): + name = models.CharField(max_length=200) + + def __str__(self) -> str: + return self.name + class Blog(models.Model): sno = models.AutoField(primary_key=True) title = models.CharField(max_length=200) @@ -8,9 +61,33 @@ class Blog(models.Model): content = models.TextField() thumbnail_img = models.ImageField(null=True, blank=True, upload_to="images/") thumbnail_url = models.URLField(blank=True, null=True) - category = models.CharField(max_length=255, default="uncategorized") - slug = models.CharField(max_length=100) + categories = models.ManyToManyField("Category", blank=False) # TODO: Auto set to uncategorized + slug = models.CharField(max_length=100, unique=True) time = models.DateField(auto_now_add=True) def __str__(self): - return self.title \ No newline at end of file + categories = list(self.categories.all()) + print(self.title, categories) + categories = [c.name for c in categories] + return f"{self.title} || {'-'.join(categories)}" + +""" def save(self, *args, **kwargs): + # Appel à super() pour sauvegarder l'objet dans la base de données + super().save(*args, **kwargs) + + categories = list(self.categories.all()) + if not categories: + # get the default category + default = Category.objects.filter(name="Uncategorized").first() + + # Vérifiez si l'objet a un ID (c'est-à-dire s'il a été enregistré dans la base de données) + if self.pk: + # Ajoutez la catégorie par défaut + self.categories.add(default) + self.save(update_fields=['categories']) + + print(f"Added category {default.name} to the post {self.title}") + print([c for c in self.categories.all()]) + else: + print("Object not saved yet, cannot add default category.")""" + diff --git a/home/urls.py b/home/urls.py index ffdd8081a..b8ea3f6d4 100644 --- a/home/urls.py +++ b/home/urls.py @@ -5,7 +5,7 @@ urlpatterns = [ path('', views.index, name='home'), path('about', views.about, name='about'), - path('contact', views.contact, name='contact'), + #path('contact', views.contact, name='contact'), path('blog', views.blog, name='blog'), path('projects', views.projects, name='projects'), path('blogpost/', views.blogpost, name='blogpost'), diff --git a/home/views.py b/home/views.py index 9537b1e77..a3cbd35a3 100644 --- a/home/views.py +++ b/home/views.py @@ -1,6 +1,5 @@ -from django.shortcuts import render, HttpResponse, HttpResponseRedirect, redirect, get_object_or_404 -from django.http import Http404 -from home.models import Blog +from django.shortcuts import render +from home.models import Blog, Project, Skill, About, Category from django.contrib import messages from django.core.paginator import Paginator from django.core.mail import send_mail @@ -8,18 +7,60 @@ import random import re + +# manually defined tags for most commons techs +TECHNOLOGIES = { + 'python': ' Python', + 'sql': ' SQL', + 'django': ' Django', + 'html': ' HTML', + 'css': ' CSS', + 'js': ' JavaScript', +} + +def get_tech_tags(tech:str): + if tech in TECHNOLOGIES: + return TECHNOLOGIES[tech.lower()] + else: + return f' {tech}' + +def get_search_categories(): + CATEGORIES_PER_LINE = 3 + tabled_categories = [[] for i in range(CATEGORIES_PER_LINE)] + + categories = Category.objects.all() + + i = row = 0 + while i < len(categories): + tabled_categories[row].append(categories[i]) + i += 1 + if i%CATEGORIES_PER_LINE == 0: + row += 1 + return tabled_categories + # Create your views here. def index (request): blogs = Blog.objects.all() random_blogs = random.sample(list(blogs), 3) - context = {'random_blogs': random_blogs} + + context = {'random_blogs': random_blogs, "categories": get_search_categories()} return render(request, 'index.html', context) def about (request): - return render(request, 'about.html') + skills = Skill.objects.all() + about = About.objects.first() + about.text = about.text.replace("\n", "
") + + NB_COLUMNS = 2 + columns = [[] for _ in range(NB_COLUMNS)] + + for i in range(len(skills)): + columns[i%NB_COLUMNS].append(skills[i]) + + return render(request, 'about.html', context={"about": about, "skills": columns, "categories": get_search_categories()}) def thanks(request): - return render(request, 'thanks.html') + return render(request, 'thanks.html', context={ "categories": get_search_categories()}) def contact (request): if request.method == 'POST': @@ -52,32 +93,62 @@ def contact (request): # return HttpResponseRedirect('/thanks') else: messages.error(request, 'Email or Phone is Invalid!') - return render(request, 'contact.html', {}) + return render(request, 'contact.html', context={"categories": get_search_categories()}) + +def projects(request): + limit = 5 + if request.method == 'GET': + if 'all' in request.GET: + if request.GET['all'] == '1': + limit = None + + projects_items = Project.objects.all().order_by('-date') + + total_projects = len(projects_items) + + # Limit the number of projects to display + if limit: + projects_items = projects_items[:limit] -def projects (request): - return render(request, 'projects.html') + all_there = len(projects_items) == total_projects + + # Convert technologies to HTML tags + for project in projects_items: + techs = project.technologies.split(',') + project.technologies = [get_tech_tags(tech) for tech in techs] + + # split the featured projects from lines to list + for project in projects_items: + project.features = project.features.split('\n') + + return render(request, 'projects.html', {'projects': projects_items, "all": all_there, "categories": get_search_categories()}) def blog(request): blogs = Blog.objects.all().order_by('-time') - paginator = Paginator(blogs, 3) + paginator = Paginator(blogs, 5) page = request.GET.get('page') blogs = paginator.get_page(page) - context = {'blogs': blogs} + context = {'blogs': blogs, "categories": get_search_categories()} return render(request, 'blog.html', context) def category(request, category): - category_posts = Blog.objects.filter(category=category).order_by('-time') + category = Category.objects.filter(name=category) + posts = Blog.objects.all() + category_posts = [] + for post in posts: + if category in post.categories.all(): + category_posts.append(post) if not category_posts: message = f"No posts found in category: '{category}'" return render(request, "category.html", {"message": message}) paginator = Paginator(category_posts, 3) page = request.GET.get('page') category_posts = paginator.get_page(page) - return render(request, "category.html", {"category": category, 'category_posts': category_posts}) + return render(request, "category.html", {"category": category, 'category_posts': category_posts, "categories": get_search_categories()}) def categories(request): - all_categories = Blog.objects.values('category').distinct().order_by('category') - return render(request, "categories.html", {'all_categories': all_categories}) + all_categories = Category.objects.values('name') + return render(request, "categories.html", {'all_categories': all_categories, "categories": get_search_categories()}) def search(request): query = request.GET.get('q') @@ -92,18 +163,19 @@ def search(request): message = "Sorry, no results found for your search query." else: message = "" - return render(request, 'search.html', {'results': results, 'query': query, 'message': message}) + return render(request, 'search.html', {'results': results, 'query': query, 'message': message, "categories": get_search_categories()}) def blogpost (request, slug): try: blog = Blog.objects.get(slug=slug) - context = {'blog': blog} + context = {'blog': blog, "categories": get_search_categories()} return render(request, 'blogpost.html', context) except Blog.DoesNotExist: - context = {'message': 'Blog post not found'} + context = {'message': 'Blog post not found', "categories": get_search_categories()} return render(request, '404.html', context, status=404) + # def blogpost (request, slug): # blog = Blog.objects.filter(slug=slug).first() # context = {'blog': blog} diff --git a/media/images/modem.png b/media/images/modem.png new file mode 100644 index 000000000..1fe88cc5f Binary files /dev/null and b/media/images/modem.png differ diff --git a/media/images/reading_translucent.png b/media/images/reading_translucent.png new file mode 100644 index 000000000..0ad1fa3ef Binary files /dev/null and b/media/images/reading_translucent.png differ diff --git a/requirements.txt b/requirements.txt index 07be74bb9..893f4cb02 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/start.sh b/start.sh new file mode 100644 index 000000000..53e29b35a --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +source .env/bin/activate +python manage.py makemigrations +python manage.py migrate +python manage.py runserver diff --git a/static/images/favicon.png b/static/images/favicon.png new file mode 100644 index 000000000..0560e8ce2 Binary files /dev/null and b/static/images/favicon.png differ diff --git a/templates/about.html b/templates/about.html index ac7108d2f..d1565a0c2 100644 --- a/templates/about.html +++ b/templates/about.html @@ -9,28 +9,30 @@

About Me

{% comment %}

About Me

{% endcomment %}

- 👋 Hi,

- - My name is Ashish and I am a highly motivated and experienced developer with a passion for creating innovative solutions for the internet.

- - After completing my schooling in 2021, I have chosen to further my education in Computer Science while concurrently taking on freelance projects and blogging to expand my skillset. I possess a diverse range of technical knowledge and experience, including proficiency in programming languages such as Python, HTML, CSS, Javascript and SQL, as well as experience with frameworks such as Tailwind CSS and Django.

- - I have applied these skills to develop a number of successful real-world applications such as a password manager, antivirus application, and full-stack blog website. Here are my projects in action.

- - I am continuously seeking new opportunities to expand my knowledge and improve my abilities as a developer, in order to assist clients in achieving their project goals. - {% comment %} I'm Ashish. I am passionate about developing things for the internet. This passion led me to learn various technologies such as Python, HTML, CSS, Javascript and so on.

- - After completing my schooling in 2021, I looked forward to pursue further studies in Computer Science. Part time I started Freelancing, Blogging and learning various programming languages. I learned HTML, CSS, Javascript, Python, SQL and various CSS Frameworks such as Tailwind CSS and Python Libraries such as Django.

- - Using these skills I developed various real world applications such as Passsave - Password Manager using Python, PyShiels - Antivirus Application using Python,Full Stack Blogging Website and more. Checkout more projects on Projects page.

- - Currently I am extending my learnings to be a skillled developer who can assist you in your projects. {% endcomment %} + {{ about.text | safe }}

Skills

{% comment %}

My Skills

{% endcomment %}
+ {% for col in skills %} + {% if col %}
+ {% for skill in col %} +
+

{{ skill.name }}

+
+
+
+
+ {% endfor %} +
+ {% endif %} + {% endfor %} + + {% comment %} +
+

HTML

@@ -49,8 +51,8 @@
+
-

CSS

@@ -65,6 +67,7 @@
+ {% endcomment %}
@@ -78,6 +81,21 @@ 0% { width: 0%; } 100% { width: 100%; } } + + {% for col in skills %} + {% for skill in col %} + + .{{ skill.tag }} { + animation: {{ skill.tag }}-animation 3s normal forwards; + } + @keyframes {{ skill.tag }}-animation { + 0% { width: 0%; } + 100% { width: {{skill.rate }}%; } + } + {% endfor %} + {% endfor %} + + {% comment %} .html { animation: html-animation 3s normal forwards; } @@ -113,9 +131,11 @@ 0% { width: 0%; } 100% { width: 80%; } } + {% endcomment %} {% comment %} ashish All Rights Reserved

+

Copyright © Petchou All Rights Reserved

+
+

Forked from the amazing work of Ashish.

@@ -73,7 +78,7 @@