Custom save method in Django - python

I have written a custom save method in my Django model class link.
I have added this method so that if i use admin panel to enter data it will get executed and i didn't have to enter data manually for that field but now when i enter data from admin panel it still asks me to enter the data but then it simply overrides my entry.Is Their any way it will not allow to enter that particular value from admin panel.
import uuid
import base64
import datetime
from django.db import models
from django.contrib import admin
#------------------------------------------------------------------------------
def _generateShortUrl():
"""
This function will generate base64 encoded id
"""
return base64.urlsafe_b64encode(uuid.uuid1().bytes)[:6]
class link(models.Model):
"""
This is a link class
"""
link = models.URLField() # To store user entered URL
hits = models.IntegerField(default=0) # How many hits for particular URL
last_used = models.DateTimeField(auto_now=True) # When URL is last used
short_url = models.CharField(max_length=6,unique=True) # base64 URL encoded id
def linkValidate(self):
timediff = datetime.datetime.now() - self.last_used
return timediff.min
def save(self, *args, **kwargs):
"""
Custom Save method for link model
"""
self.short_url = _generateShortUrl()
super(link, self).save(*args, **kwargs)
class user_info(models.Model):
"""
This is a user_info class
"""
user_agent = models.TextField() # Stores user_agent name used by user
user_ip = models.ManyToManyField(link) # Stores user's IP
def userIP(self):
"""Method to return tags related to store"""
return ','.join([t.link for t in self.link.all()])
#------------------------------------------------------------------------------
class linkAdmin(admin.ModelAdmin):
"""
link admin class
"""
list_display = ('link','hits','short_url','last_used',
'linkValidate')
ordering = ('hits',)
class userInfoAdmin(admin.ModelAdmin):
"""
user_info admin class
"""
list_display = ('user_agent','userIP')
#------------------------------------------------------------------------------
admin.site.register(link,linkAdmin)
admin.site.register(user_info,userInfoAdmin)

You need to tell your Model & ModelAdmin not to require those fields.
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#django.contrib.admin.ModelAdmin.exclude

You can change you field definition to:
short_url = models.CharField(max_length=6,unique=True, default=_generateShortUrl)
or change you linkAdmin adding :
exclude = ('short_url',)

You should try assigning a value to shortURL when the instance is created rather then when stored.

Related

add more model info to the JWT token

I'm creating a messaging app. I have 3 models in my backend Django. I have a profile model that stores user & which room they are connected with(so that every time they log in, their rooms will pop up in side bar like WhatsApp). in profile model I have a many to many relationship with Room model that stores rooms list. As I'm using JWT web token for authentication, I want users profile model/rooms like of that user to be added in the token. so that I can fetch the info from token directly but I don't know how to add that fields info into the token views. I've already customized my token obtain view where I added users name as extra but I need to add the list of rooms too.
Thanks in advance for helping.
#model.py
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.contrib.auth.models import User
# Create your models here.
class Room(models.Model):
name = models.CharField(max_length=100,blank=True,null=True)
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
rooms = models.ManyToManyField(Room)
class Message(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE,blank=False,null=True)
message = models.TextField(max_length=500,blank=False,null=True)
name = models.CharField(max_length=100,blank=True,null=True)
room = models.ForeignKey(Room,on_delete=models.CASCADE,null=True)
time = models.DateTimeField(auto_now_add=True)
received = models.BooleanField(default=False,null=True)
#views.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['name'] = user.username
**here i want to have a token['room'] that will return me the list of rooms**
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
At first get the Profile: without reverse relation it can be retrieved via
profile = Profile.objects.get(user=user)
Then get the rooms:
rooms = profile.rooms.all()
At the end you should consider what information about the rooms you are storing into the token. The name can be blank and does not need to be unique. So it is better to store the id:
token['rooms'] = [r.id for r in rooms]
well, i found out by myself. it's very easy. just get all the profile data of that user. now iterate through the list of the rooms using rooms.all()
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['name'] = user.username
room = Profile.objects.get(user=user)
token['rooms'] = [[r.name,r.id] for r in room.rooms.all()]
print([r for r in room.rooms.all()])
# ...
return token

Sending back file-content instead of href with django-rest-framework?

I have a model for articles which takes a field FileField which is supposed to be a markdown file for the user to load their article already. I expose the api with a ModelViewSet.
This is saved to my media folder. I could fetch the content from the client side by GETing it from the href of course but that would mean 2 requests to my server:
get article info (title, content- this is the md, date published, description, etc.. ).
get content from the link.
But i'm wondering if there's a way to tell django to just send the content of the file instead of the href when it responds to a requestion for the article item.
Here's my model and api:
# ---------------------- #
# src/articles/models.py #
# ---------------------- #
from os.path import splitext
from uuid import uuid4
from django.db import models
# Create your models here.
def hashFilename(instance, name):
ext = splitext(name)[1]
return "articles/{}{}".format(uuid4(), ext)
def hashImageFilename(instance, name):
ext = splitext(name)[1]
return "images/{}{}".format(uuid4(), ext)
class Article(models.Model):
title = models.CharField(("title"), max_length=100)
content = models.FileField("content", upload_to=hashFilename)
description = models.TextField(("description"), default='')
uploadDate = models.DateTimeField(("uploadDate"), auto_now=True)
lastModified = models.DateTimeField(("uploadDate"), auto_now=True)
publicationDate = models.DateField("publicationDate")
image = models.ImageField("image", upload_to=hashImageFilename)
def __str__(self):
return self.title
# ------------------------- #
# src/articles/api/views.py #
# ------------------------- #
from rest_framework.viewsets import ModelViewSet
from ..models import Article
from .serializers import ArticleSerializerFull, ArticleSerializerShort
class ArticlesViewSet(ModelViewSet):
queryset = Article.objects.all()
def get_serializer_class(self):
if self.action == 'list':
serializer = ArticleSerializerShort
else:
serializer = ArticleSerializerFull
return serializer
queryset = Article.objects.all()
Defining a serializers.SerializerMethodField--(DRF Doc) method will do the job.
class ArticleSerializer(serializers.ModelSerializer):
content = serializers.SerializerMethodField()
def get_content(self, article):
return article.content.file.read()
class Meta:
fields = '__all__'
model = Article
Alternatively, you could achieve the same by overriding the to_representation method of the serializer.
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Article
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['content'] = instance.content.file.read()
return rep
Update-1
From this comment, I hope you need a live markdown editor in Django Admin rather than a FileField.
So, Use any of these markdown packages to get a live view in the Django Admin. These packages are using models.TextField to store the markdown content. So that you could read the content from the field in anywhere just like any other model fields

How to Restricting one like per post in Django

I am a beginner in Django currently. I want to restrict one like per post in my so Posting kind of app, where you can post a text and user can like or dislike the same. Now I enabled Login and I want a logged in user to like a post only once and I am unsuccessful in doing so.
Models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Todo(models.Model):
text = models.CharField(max_length=100)
complete = models.BooleanField(default=False)
like=models.IntegerField(default=0)
user = models.ForeignKey(User)
def __str__(self):
return self.text
Views,py
def like(request,todo_id):
if Todo.objects.filter(todo_id = todo_id, user_id=request.user.id).exists():
return redirect('index')
else:
todo = Todo.objects.get(pk=todo_id)
todo.like += 1
todo.save()
return redirect('index')
What basically I was missing is saving the likes corresponding to user. And this problem is easily solved by creating a model with User and main model as Foreign Key's as pointed out by Paolo.
models.py
from django.db import models
from django.contrib.auth.models import User
class Todo(models.Model):
text = models.CharField(max_length=100)
complete = models.BooleanField(default=False)
def __str__(self):
return self.text
class Todo_likes(models.Model):
todo = models.ForeignKey(Todo, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
from django.shortcuts import render,redirect
from .models import Todo,Todo_likes
from .forms import TodoForm
def like(request,todo_id):
user = request.user
todo = Todo.objects.get(id=todo_id)
like, created = Todo_likes.objects.get_or_create( # get_or_create will
# itself do the job of
# finding and creating if not exist
user = user,
todo = todo
)
if not created:
return redirect('index') #I don't wanted to show any error if existed earlier.
#I just wanted to redirect.
else:
return redirect('index')
And then counting my number of likes by simply going to my Index view and inserted
def index(request):
form = TodoForm()
todo_list = Todo.objects.order_by('id')
likes = Todo_likes.objects.count() # Adding this line
context= {'todo' : todo_list, 'form':form, 'likes' : likes}
return render(request,'todolistapp/index.html',context)
And displaying 'likes' object in my template.
I would like to give it a try and expand on my comment.
My suggestion is, to create a new model Todo_Likes which will have both Todo and User model as Foreign Keys to be able to track who liked a Todo.
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Todo(models.Model):
text = models.CharField(max_length=100)
complete = models.BooleanField(default=False)
# Got rid of the user FK and like since I would count likes using
# Todo_Like.objects.count()
def __str__(self):
return self.text
class Todo_Like(models.Model):
todo = models.ForeignKey(Todo)
user = models.ForeignKey(User)
views.py
def like(request,todo_id):
# Get user
user = request.user
# Get Todo object
todo = Todo.objects.get(id=todo_id)
# Check/Validate if current user has already liked the Todo.
# If not create a new Todo_Like object.
todo_like = Todo_Like.objects.get(todo=todo, user=user)
if not todo_like:
# If the user haven't like the Todo object yet create a Todo_Like object.
t = Todo_Like (
todo=todo,
user=user
)
t.save()
else:
# If the user has already liked the Todo then proceed to do
# Whatever you want, redirect? message?
I would also like to answer a question in advance.
You might ask how would you count the likes now? Answer: Make use of Todo_Like.objects.count(), and make sure you count it by Todo.

The use of ForeingKey on django when I create an object is also creating an object from the foreignkey model

I have a function on the main app of the website i'm working on that creates a User - the model name is "Usuario" (different from the default User model that django provides). The model is:
class Usuario(models.Model):
email = models.CharField(max_length=250)
data_cadastro = models.DateTimeField(default=now)
telefone = models.CharField(validators=[phone_regex], max_length=17, blank=True) # validators deve ser uma lista
renda = models.DecimalField(default=0,max_digits=14,decimal_places=2)
longitude = models.DecimalField(default=0,max_digits=20,decimal_places=12)
latitude = models.DecimalField(default=0,max_digits=20,decimal_places=12)
(the names of the fields are in Portuguese)
This model is connected to a different one in another app by a simple foreing key (usuario). The other model is:
from core.models import Usuario
class Simulacao(Usuario):
usuario = models.ForeignKey(Usuario, related_name="Usuario_contas", on_delete=models.CASCADE)
data_simulacao = models.DateTimeField(default=now, verbose_name="Data simulação")
tem_conta = models.BooleanField(default=False)
class Meta:
verbose_name = 'Simulação'
verbose_name_plural = 'Simulações'
def __str__(self):
return self.nome
def __unicode__(self):
return self.nome
The function where I create the "Simulacao" object is this one:
from .models import Simulacao, Tipo_Pessoa
from core.models import Usuario
from django.utils.timezone import now
def cadastro_simulacao(form, email):
nova_simulacao = Simulacao.objects.create(usuario = Usuario.objects.get(email = email), tem_conta=form['tem_conta'])
usuario.Usuario_contas_set.add(nova_simulacao)
usuario.save()
All i've done is based on the Django documentation: https://docs.djangoproject.com/pt-br/2.1/topics/db/examples/many_to_one/
I have also tried the function like that:
from .models import Simulacao, Tipo_Pessoa
from core.models import Usuario
from django.utils.timezone import now
def cadastro_simulacao(form, email):
nova_simulacao = Simulacao.objects.create(usuario = Usuario.objects.get(email = email), tem_conta=form['tem_conta'])
nova_simulacao.save()
However, when it's done, a new object of "Usuario" is created with no information (no email, etc, all fields are created blank) and the information of the user doesn't appear properly at the "simulacao" model - the information that was supposed to be inherited from the "usuario" shows up blank too.
Why is that happening? What I want is to catch the information of the User that has the same email and create a "simulacao" object that is connected to this user. I don't want a new object of user. How can I do that?
Your Simulacao inherits from Usario. It shouldn't do that; you have a separate ForeignKey. Remove that inheritance so that Simulacao inherits directly from models.Model.

Use of ._meta Django

I want to ask about the use of ._meta in this code ? I didn't find a documentation that explains the use of .meta
def resend_activation_email(self, request, queryset):
"""
Re-sends activation emails for the selected users.
Note that this will *only* send activation emails for users
who are eligible to activate; emails will not be sent to users
whose activation keys have expired or who have already
activated.
"""
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
for profile in queryset:
if not profile.activation_key_expired():
profile.send_activation_email(site)
resend_activation_email.short_description = _("Re-send activation emails")
_meta as the name implies stores the meta data of the model. Say, you want to loop over all the fields of a model, then you can use model._meta.get_fields().
By the way, meta options are covered in the documentation -
Model Meta options documentation
You can also use ._meta in testing.
Consider the below models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
published = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.author} {self.title} {self.content}"
You could use ._meta to verify label names of a post (in test_models.py) like:
from datetime import datetime
from django.test import TestCase
from django.contrib.auth.models import User
from blog.models import Post
class TestBlogModels(TestCase):
def setUp(self):
author = User.objects.create(username='TestUser1')
self.post = Post.objects.create(
author=author,
title='Test Post Title 1',
content='Test content for title 1'
)
....
def test_blog_author_label(self):
author_field = Post._meta.get_field('author').verbose_name
self.assertEqual(author_field, "author")
def test_blog_title_label(self):
title_field = Post._meta.get_field('title').verbose_name
self.assertEqual(title_field, "title")
def test_blog_content_label(self):
content_field = Post._meta.get_field('content').verbose_name
self.assertEqual(content_field, "content")
def test_blog_title_max_length(self):
title_field_length = Post._meta.get_field('title').max_length
self.assertEqual(title_field_length, 100)
...
(verbose_name strips django.db.models.fields to the field speicified in models.py)

Categories

Resources