Trying this syntax to populate a column from other columns.
SELECT CONCAT('Oferta,',`id`, ',', `nazwa`) as slug FROM `maszyny`;
Here 'Oferta' is hard coded string. Its showing a result with above concat format but column 'slug' didnt populate with data.
What i am missing?
I've added some comments to your code, please let me know if something's still unclear so I can update it with more information:
class ArticlePkAndSlug(models.Model):
title = models.CharField(max_length=settings.BLOG_TITLE_MAX_LENGTH)
# editable=False means that slug will be created only once and then it won't be updated
slug = models.SlugField(default="", editable=False, max_length=settings.BLOG_TITLE_MAX_LENGTH)
def get_absolute_url(self):
# this method returns an absolute URL to object details, which consists of object ID and its slug
kwargs = {"pk": self.id, "slug": self.slug}
return reverse("article-pk-slug-detail", kwargs=kwargs)
def save(self, *args, **kwargs):
value = self.title
self.slug = slugify(value, allow_unicode=True)
# here the title of an article is slugifyed, so it can be easily used as a URL param
super().save(*args, **kwargs)
Related
I'm working on a project using Flask and a PostgreSQL database, with SQLAlchemy.
I have Group objects which have a list of User IDs who are members of the group. For some reason, when I try to add an ID to a group, it will not save properly.
If I try members.append(user_id), it doesn't seem to work at all. However, if I try members += [user_id], the id will show up in the view listing all the groups, but if I restart the server, the added value(s) is (are) not there. The initial values, however, are.
Related code:
Adding group to the database initially:
db = SQLAlchemy(app)
# ...
g = Group(request.form['name'], user_id)
db.session.add(g)
db.session.commit()
The Group class:
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.dialects.postgresql import ARRAY
class Group(db.Model):
__tablename__ = "groups"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
leader = db.Column(db.Integer)
# list of the members in the group based on user id
members = db.Column(ARRAY(db.Integer))
def __init__(self, name, leader):
self.name = name
self.leader = leader
self.members = [leader]
def __repr__(self):
return "Name: {}, Leader: {}, Members: {}".format(self.name, self.leader, self.members)
def add_user(self, user_id):
self.members += [user_id]
My test function for updating the Group:
def add_2_to_group():
g = Group.query.all()[0]
g.add_user(2)
db.session.commit()
return redirect(url_for('show_groups'))
Thanks for any help!
As you have mentioned, the ARRAY datatype in sqlalchemy is immutable. This means it isn’t possible to add new data into array once it has been initialised.
To solve this, create class MutableList.
from sqlalchemy.ext.mutable import Mutable
class MutableList(Mutable, list):
def append(self, value):
list.append(self, value)
self.changed()
#classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableList):
if isinstance(value, list):
return MutableList(value)
return Mutable.coerce(key, value)
else:
return value
This snippet allows you to extend a list to add mutability to it. So, now you can use the class above to create a mutable array type like:
class Group(db.Model):
...
members = db.Column(MutableList.as_mutable(ARRAY(db.Integer)))
...
You can use the flag_modified function to mark the property as having changed. In this example, you could change your add_user method to:
from sqlalchemy.orm.attributes import flag_modified
# ~~~
def add_user(self, user_id):
self.members += [user_id]
flag_modified(self, 'members')
To anyone in the future: so it turns out that arrays through SQLAlchemy are immutable. So, once they're initialized in the database, they can't change size. There's probably a way to do this, but there are better ways to do what we're trying to do.
This is a hacky solution, but what you can do is:
Store the existing array temporarily
Set the column value to None
Set the column value to the existing temporary array
For example:
g = Group.query.all()[0]
temp_array = g.members
g.members = None
db.session.commit()
db.session.refresh(g)
g.members = temp_array
db.session.commit()
In my case it was solved by using the new reference for storing a object variable and assiging that new created variable in object variable.so, Instead of updating the existing objects variable it will create a new reference address which reflect the changes.
Here in Model,
Table: question
optional_id = sa.Column(sa.ARRAY(sa.Integer), nullable=True)
In views,
option_list=list(question.optional_id if question.optional_id else [])
if option_list:
question.optional_id.clear()
option_list.append(obj.id)
question.optional_id=option_list
else:
question.optional_id=[obj.id]
I am using the YouTube Data API to create an object, but when I create a single object, it creates two objects - one with the proper details and one that is blank. How can I resolve this issue?
before creating object
after creating single object
I am trying with the following code.
view.py
class VideoCreateView(CreateView):
model = Video
form_class = VideoForm
template_name = "videos/video_form.html"
def form_valid(self, form):
video = Video()
video.url = form.cleaned_data['url']
parse = urllib.parse.urlparse(video.url)
video_id = urllib.parse.parse_qs(parse.query).get('v')
if video_id:
video.youtube_id =video_id[0]
response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={video_id[0]}&key={YOUTUBE_API_KEY}')
json = response.json()
items = json["items"]
assert len(items) <= 1
if len(items):
title = items[0]["snippet"]["title"]
video.title = title
video.save()
else:
title = "N/A"
return super().form_valid(form)
models.py
class Video(models.Model):
title = models.CharField(max_length=255)
url = models.URLField()
youtube_id = models.CharField(max_length=255)
slug = models.SlugField(blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("videos:video_detail", kwargs={"slug":self.slug})
def video_pre_save_reciever(sender,instance,*args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(video_pre_save_reciever,Video)
if more code is require than tell me in comment , i will update my question with that information.
The view VideoCreateView inherits CreateView. CreateView inherits ModelFormMixin which defines form_valid method.
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
return super().form_valid(form)
You save the video object and call the super form_valid which saves the form(in turn creating a model object) again. Hence, causing a double creation. I suggest modifying the form and passing it to super instead of manually saving it.
Another option is to inherit the View with django.views.generic.View. This would avoid form save.
I suggest you follow the first approach.
I have solve this my problem by removing all the form_valid code from the views and add that inside the model
class Video(models.Model):
title = models.CharField(max_length=255)
url = models.URLField()
youtube_id = models.CharField(max_length=255)
slug = models.SlugField(blank=True)
def save(self,*args,**kwargs):
parse = urllib.parse.urlparse(self.url)
video_id = urllib.parse.parse_qs(parse.query).get('v')
if video_id:
self.youtube_id =video_id[0]
response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={video_id[0]}&key={YOUTUBE_API_KEY}')
json = response.json()
title = json["items"][0]["snippet"]["title"]
self.title = title
super(Video,self).save(*args,**kwargs)
I am trying to raise validation error for the entry field in the forms.py
My models.py
class StudBackground(models.Model):
stud_name=models.CharField(max_length=200)
class Student(models.Model):
name=models.CharField(max_length=200)
My forms.py
class StudentForm(forms.ModelForm):
name = forms.CharField(max_length=150, label='',widget= forms.TextInput)
class Meta:
model = Student
fields = ['name',]
where i tried to apply clean method :
def clean_student(self,*args,**kwargs):
name=self.cleaned_data.get("name")
if not studBackground.stud_name in name:
raise forms.ValidationError ( "It is a not valid student")
else: return name
I tried to incorporate stud_name from the StudBackground model to the form but it does not work it raises following error when i try to type student name that is not in DB:
Profiles matching query does not exist
however it supposed to return near the name field "It is a not valid student"
How to make it work? What is the wrong with the code?
You can try like this:
def clean_student(self):
name=self.cleaned_data.get("name")
if not StudBackground.objects.filter(stud_name=name).exists():
raise forms.ValidationError("It is a not valid student")
return name
I am using filter(...) function from queryset to check if a name exists in StudBackground. I am also running exists() to check if entry exists in DB.
Update
I think your indentations are not correct for the view. But, you can try like this:
def home(request):
form = StudentForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
instance = form.save()
name = instance.name
class_background=StudBackground.objects.get(stud_name=name)
context={'back':class_background}
return render(request, 'class10/background.html', context)
# Now let us handle if request type is GET or the form is not validated for some reason
# Sending the form instance to template where student form is rendered. If form is not validated, then form.errors should render the errors.
# How to show form error: https://docs.djangoproject.com/en/3.0/topics/forms/#rendering-form-error-messages
return render(request, 'your_student_form_template.html', context={'form':form})
I'm having a problem with django rest framework.
My front is posting data to drf, and one of the fields could be null or an empty string "".
# models.py
class Book(models.Model):
title = models.CharField(max_length=100)
publication_time = models.TimeField(null=True, blank=True)
# serializers.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'publication_time')
publication_time could either be blank or "".
The blank case works, in fact when I post a json {"title": "yeah a book", "publication_time": none} everything is fine.
When I send {"title": "yeah a book", "publication_time":""} I do get a validation error "Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]."
I've tried to add a field validator to the serializer class:
def validate_publication_time(self, value):
if not value:
return None
Or even using the extra_kwargs
# ....
def empty_string_to_none(value):
if not value:
return None
# ....
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'publication_time')
extra_kwargs = {'publication_time': {'validators' : [empty_string_to_none]} }
What I am trying to do is to transform an empty string to None (that should be accepted by the serializer and the model) before any validation occurs or as the first validation rule.
PROBLEM:
The problem is that the validate_publication_time is never called and I get a validation error before even hitting the function. As I've understood there is a specific order in which the validators run, but now I have no idea how to solve my issue.
QUESTION:
What I want to do is to actually clean the data in order to transform "" into None before any validation is run. Is it possible? How?
EDIT:
This is the representation of my serializer:
# from myapp.serializers import BookSerializer
# serializer = BookSerializer()
# print repr(serializer)
# This is the print result:
BookSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(max_length=100)
publication_time = TimeField(allow_null=True, required=False)
So as you can see the publication_time field could be null, isn't it?
I had the same problem and finally found a solution.
In order to deal with '' before the error occurs, you need to override the to_internal_value method:
class BookSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
if data.get('publication_time', None) == '':
data.pop('publication_time')
return super(BookSerializer, self).to_internal_value(data)
Have you tried to override serialization behavior? What you need is override .to_internal_value(self, data)
the kwarg is allow_null and allow_blank not null and blank.
You can override serializer's save method where you would check if the value is an empty string and if it is then set it to Null.
In your serializer (untested):
def save(self, *args, **kwargs)
if self.publication_time == "":
self.publication_time = Null
super.save(*args, **kwargs)
Or, you can do like that in a view(this is how I do that):
def perform_update(self, serializer):
publication_time = self.kwargs['publication_time']
if publication_time == "":
publication_time = Null
serializer.save(publication_time=publication_time)
only then you'll also need to overwrite perform_create if you also need this when you POST, not only when PUT
Answer of #Ivan Blinov is correct, except you should allow the data to be mutable, otherwise you get this error:
AttributeError: This QueryDict instance is immutable
so the complete answer is:
class BookSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
if data.get('publication_time', None) == '':
data._mutable = True
data.pop('publication_time')
return super(BookSerializer, self).to_internal_value(data)
I've got a model method that calculates the score of votes using the HN ranking algorithm in my model that also caches each score. calculate_score() is the main thing to focus on here.
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'),
max_length=255,
choices=tuple([(media.name.lower(), media.value) for media in MediaTypes]),
blank=False)
title = models.CharField(_('Title'), max_length=100, blank=False)
url = models.URLField(_('Link'), blank=False)
description = models.TextField(_('Description'))
flagged = models.BooleanField(_('Is Flagged for Review'), default=False)
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
by_votes = VoteCountManager()
objects = models.Manager()
def __str__(self):
return self.submission_type + ': ' + self.title
def get_absolute_url(self):
return reverse('submit_detail', args=[
str(self.submission_type),
str(self.id),
])
def get_vote(self):
"""
Returns the number of votes associated with a particular submission
:return: int
"""
return self.submission_votes.count()
def calculate_score(self):
"""
This is a variation of the HN ranking algorithm
:return: score
"""
secs_in_hour = float(60 * 60)
g = 1.2
delta = timezone.now() - self.date_submitted
item_hour_age = delta.total_seconds() / secs_in_hour
votes = self.submission_votes.count() - 1
score = votes / pow((item_hour_age + 2), g)
cached = cache.get('s{0}'.format(self.pk))
if not cached:
cache.set('s{0}'.format(self.pk), score, 30)
return score
I'm using the djangorestframework that serializes my model:
class SubmissionSerializer(serializers.HyperlinkedModelSerializer):
score = serializers.SerializerMethodField('rank')
class Meta:
model = Submission
def rank(self, obj):
return obj.calculate_score()
I have two ways that I think I can solve my problem, but I don't know how to do either of them, and I'm not sure which one is best. Since I'm caching the score for each individual submission. I was trying to order the submissions in a ListView like this pulling from the cache:
class SubmissionList(ListView):
model = Submission
def get_queryset(self):
return super(SubmissionHotList, self).get_queryset().annotate(
score=cache.get('s{0}'.format(x) for x in Submission.pk)
votes=models.Count('submission_votes'),
).order_by(
'-score', '-votes',
)
But I found that order_by only works on the database level, and I would have to create a database field for the calculated score, which I would like to avoid if possible. My other possibility is using the serialized data in my API for Submission in this ListView, but I'm not sure if APIs are only there for external applications, or if I could use them in the same application that the API is generated.
I guess my question is. Would it be better to get the cached objects to list each submission in a particular order, or could I use the API to accomplish this? And if I used the API, how could I parse and order the JSON data in the ListView, while keeping votes as a secondary ordering mechanism?
if you want to display all items without pagination, you will not lose any performance if you do something like
import operator
class SubmissionList(TemplateView):
template_name = "submissions.html"
def get_context_data(self, **kwargs):
context = super(SubmissionList, self).get_context_data(**kwargs)
submissions = Submission.objects.all().annotate(
votes=models.Count('submission_votes')
)
sorted_submissions = sorted(submissions, key=lambda x:(cache.get('s{0}'.format(x.pk)), x.votes), reverse=True)
context['submissions'] = sorted_submissions
return context