Handle Unique field in Flask WTF - flask

I am using Flask WTF for my application and created a form.
I have defined unique in model.
Flask WTF:
packet_id = StringField("Packet ID", validators=[DataRequired()])
Flask SQL-Alchemy Model : packet_id = db.Column(db.String(120), unique=True, nullable=False)
and handling it like this
def validate_packet_id(self, packet_id):
if packet_id.data is None:
raise ValidationError("Name field is required.")
else:
packet = TableName.query.filter_by(packet_id=packet_id.data).first()
if packet:
raise ValidationError('That packet ID already exists. Please choose a different one.')
This code works perfectly with the post method, but when I use update It shows an error that packet_id already exists.
Can anyone help me how to check the packet_id in DB except this current row id?

Related

Django framework form is trying to insert a new record instead of updating

Django 1.11.4 python 3.6
I have a default Django framework form I use for both update and create records for a given model. The primary key ("id" field) is generated by Django. The rest is defined in my model (see below). The model is subclassed from AuditModel class which overloads save method.
Everything works at this point, i.e. I can create new records or update existing records using standard Django forms interface.
class Product(AuditModel):
internal_id = models.CharField(max_length=100, null=True, blank=True, help_text="Internal ID")
external_id = models.CharField(max_length=100, null=False, blank=False, help_text="External ID", verbose_name="External ID")
label = models.ForeignKey(Label, help_text="Label")
class AuditModel(models.Model):
created = models.DateTimeField(null=True,editable=False)
updated = models.DateTimeField(null=True,editable=False)
def save(self, *args, **kwargs):
date = timezone.localtime()
if self._state.adding :
self.created = date
self.updated = date
super(AuditModel, self).save()
My question: I would like external_id to be unique (but not a primary key), i.e.
external_id = models.CharField(max_length=100, unique=True, null=False, blank=False, help_text="External ID", verbose_name="External ID")
Once I added unique=True to the definition of the external_id field, the behaviour of the view changes: on attempt to update existing record I get an error message next to external_id textbox "Product with this External ID already exists." and no change happens in the DB. Somehow presence of unique=True in the definition of the external_id field make Django to think that I am not editing an existing record but arrived to this form to create a new entry.
The url I arrived to the screen with is correct, i.e /product/<some id here>/change/, not /product/add
In the DB all the existing values in external_id field are non-null (no empty strings either) and unique.
If I understood correctly by adding some debug, the error "Product with this External ID already exists" happens BEFORE save() is even called, like unique=True invokes some Django data validator that happened to be unaware of the current action (update vs insert) and the view is just reloaded with an error.
Solved - The problem was caused by commented out line # instance._state.adding = False inside def from_db() method of a superclass. No idea why it got commented out.

Operational error when I tried to create new tables

So, before adding a few columns I had username, password and email and it was working fine. But when I tried to add a few more things such as age, gender, phone and address I got this error:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: user [SQL: 'SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.password AS user_password, user.age AS user_age, user.gender AS user_gender, user.phone AS user_phone, user.address AS user_address \nFROM user \nWHERE user.username = ?\n LIMIT ? OFFSET ?'] [parameters: ('agam-kashyap', 1, 0)] (Background on this error at: http://sqlalche.me/e/e3q8)
The error seems to be in these lines:
#app.route("/register",methods = ['GET','POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email = form.email.data, password = form.password.data, age = form.age.data, gender = form.gender.data, phone = form.phone.data, address = form.address.data)
db.create_all()
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))
return render_template('register.html', title= 'Register' , form = form)
Also, here is my User class:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
age = db.Column(db.String(3), nullable=False)
gender = db.Column(db.String(6), nullable=False)
phone = db.Column(db.String(10), nullable=False)
address = db.Column(db.String(200), nullable=False)
How can I solve this issue?
In keeping with Dave W. Smith's suggestion, the error is probably due to the fact that you did not perform any database migration operations for the new changes to take effect.
When a database model changes, as it does for you, the database must of course be updated. This is what you want to accomplish visibly.
So, you should note that the particularity with SQLAlchemy is that it creates tables from models only when they already exists.
It means that if you want to add new things to the database, modify or delete fields from your model, you have to destroy the old tables and recreate everything from scratch.
So the solution to work around this problem is to go through a database migration framework. Like a source code version control tool, a database migration framework tracks any changes that occur on the database schema. So it can apply incremental changes to the database.
For SQLAlchemy, there is an excellent database migration tool, I named: Alembic. But since you use Flask, you do not have to manipulate Alembic directly; There is a Flask extension that handles SQLAlchemy database migrations for Flask applications using Alembic: flask-migrate
As you are still a beginner with Flask, I recommend this excellent Miguel Grinberg's tutorial: the flask mega tutorial - part-iv :database. It will teach you the basics needed to work with Flask SQLAlchemy

Save additional data to Django database

I am using Django Channels and I want to be able to save additional fields to the database along with the json data of the post.
I have a foreign key in my Postmie model that points to email field in my user model. Postmie is the model responsible for saving posts to the database. The Foreign key creates a field called email_id. While I am saving posts to the database I want to also grab the email of the user making the post and save it in the database as well. How can I go about doing this? I am not using Django forms.
My Postmie model is the same as the one in the Django Channels tutorial Post found here, the only difference is that my model has an extra foreign key pointing to the email field in my user model.
email=request.user.email does not work. I was thinking about putting the email in a hidden field, but that does not seem safe to me.
The method I am using is practically the same method in the Django Channels tutorial found here consumers.py. Everything works but I am not able to enter other fields in the database for posts.
def save_post(message, slug, request):
"""
Saves vew post to the database.
"""
post = json.loads(message['text'])['post']
email = request.user.email
feed = Feed.objects.get(slug=slug)
Postmie.objects.create(feed=feed, body=post email_id=email)
Postmie model:
#python_2_unicode_compatible
class Postmie(models.Model):
# Link back to the main blog.
feed = models.ForeignKey(Feed, related_name="postmie")
email = models.ForeignKey(Usermie,
to_field="email",
related_name="postmie_email", max_length=50)
subject = models.CharField(max_length=50)
classs = models.CharField(max_length=50, null=True, blank=True)
subclass = models.CharField(max_length=50, null=True, blank=True)
title = models.CharField(max_length=60, null=True, blank=True)
body = models.TextField()
date_created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return "#%i: %s" % (self.id, self.body_intro())
def post_email(self):
return self.email
def post_subject(self):
return self.subject
def post_datetime(self):
return self.datetime
def get_absolute_url(self):
"""
Returns the URL to view the liveblog.
"""
return "/feed/%s/" % self.slug
def body_intro(self):
"""
Short first part of the body to show in the admin or other compressed
views to give you some idea of what this is.
"""
return self.body[:50]
def html_body(self):
"""
Returns the rendered HTML body to show to browsers.
You could change this method to instead render using RST/Markdown,
or make it pass through HTML directly (but marked safe).
"""
return linebreaks_filter(self.body)
def send_notification(self):
"""
Sends a notification to everyone in our Liveblog's group with our
content.
"""
# Make the payload of the notification. We'll JSONify this, so it has
# to be simple types, which is why we handle the datetime here.
notification = {
"id": self.id,
"html": self.html_body(),
"date_created": self.date_created.strftime("%a %d %b %Y %H:%M"),
}
# Encode and send that message to the whole channels Group for our
# feed. Note how you can send to a channel or Group from any part
# of Django, not just inside a consumer.
Group(self.feed.group_name).send({
# WebSocket text frame, with JSON content
"text": json.dumps(notification),
})
def save(self, *args, **kwargs):
"""
Hooking send_notification into the save of the object as I'm not
the biggest fan of signals.
"""
result = super(Postmie, self).save(*args, **kwargs)
self.send_notification()
return result
Assuming that Usermie is your user model. This means that you have AUTH_USER_MODEL='yourapp.Usermie' in settings.py
If you are not using to_field, You can do,
I think you need to do the following
Postmie.objects.create(feed=feed, body=post email=request.user)
Or you can do
Postmie.objects.create(feed=feed, body=post email_id=request.user.id)
You should know that every foreign key is usually represented on the database with the name of the field appended with _id. This is how Django put the foreign key. Usually you should use the ORM of Django directly.
If you are using to_field:Only in Django > 1.10
According to the documentation, email should be unique.
If to_field is changed after Postmie is created. Please make sure that all values in the column have the new corresponding value.

Changing primary key in a form creates two entries in DB, one with old primary key and other with new primary key

Code:
Model:
class Machines(models.Model):
name = models.CharField(max_length=100, verbose_name="hostname", help_text="Host name of the machine")
ip_addr = models.CharField(max_length=50, primary_key=True, help_text="IP address of the machine")
.... have many other fields.
Form:
Using forms.ModelForm to create a form
View:
def save(request):
if request.method == "POST":
ip = request.POST.get("ip")
machine = get_object_or_404(Machines, ip_addr=ip)
form = MachineForm(instance=machine, data=request.POST)
if form.is_valid():
if form.has_changed():
form.save()
context = {"message": "Updated successfully"}
else:
context = {"message": "No data to update"}
return render_to_response("edit.html", context, context_instance=RequestContext(request))
If I change "name" field, form.save() updates the current object properly. But, if I change "ip_addr" field which is primary key, form.save() creates two entries one with old primary key other with new primary key.
If we do same in MySQL(BTW, I am using MySQL as DB)
update machines_table set ip_addr="10.1.1.1" where ip_addr="10.1.1.2";
It works fine, there will not be any duplicate entry.
Can you please help me out.
What you're doing is updating private key. Django creates new instance in this case. It is is actually Django's way to copy a record. The INSERT not UPDATE query is performed by ORM.
If you change your code to:
class Machines(models.Model):
…
ip_addr = models.CharField(max_length=50, unique=True, …
…
Django will interprete your edit action correctly as the primary key won't change.
Next time use django-debug-toolbar to see what query is performed by Django ORM.

Trouble getting object in Django's models

I have a user submission form, and the user supplies his name and email. Each email is associated to a network (by an admin, prior to user registration), and based upon a user's email he will be assigned to that network.
Here is what the models.py looks like --
class Network(models.Model):
network = models.CharField(max_length=50)
location = models.CharField(max_length=50)
class EmailList(models.Model):
email = models.EmailField(blank=True)
network = models.CharField(max_length=50)
class User(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(max_length=50)
network = models.ForeignKey(Network)
And in the views.py this is what I am using to try and insert the record into the database --
User.objects.create(
name = cd['name']
email=cd['email'],
network= EmailList.objects.filter(email=request.POST.get('email'))['network'])
However, I am getting an exception TypeError from the network= line. What should the syntax be here to 'pull' and insert the network associated with the email into the database? What am I doing incorrectly
Update
Here is the code I used in views.py to get it working.
email_list = EmailList.objects.get(email=cd['email'])
network= Network.objects.get(network=email_list.network)
User.objects.create(
name=cd['name'],
email=cd['email'],
network=network)
When I tried setting the variable email = cd['email'] and then defining the email_list using that variable like so -- email_list = EmailList.objects.get(email=email), it would raise an exception saying Queryset not found in EmailList and would pass a unicode string.
Why does defining the variable before passing it in this case create a unicode string, whereas passing the expression directly in does not?
Once again, you shouldn't be using the post data directly.
Filter returns a queryset not an instance. That looks like what is causing your problem.
Also you need to get a network instance instead of a string to set to User.network:
if email for EmailList and network for Network models are unique you can do the following, if not and there are multiple entries using get will raise an Error.
name = cd.get('name')
email = cd.get('email')
email_list = EmailList.objects.get(email=email)
network = Network.objects.get(network=email_list.network)
User.object.create(name=name, email=email, network=network)